Использование строго типизированного ActionLink, когда метод действия не принимает примитивный тип - PullRequest
1 голос
/ 15 декабря 2008

Кто-нибудь знает, как я мог бы сделать что-то вроде:

Html.ActionLink (c => c.SomeAction (новый MessageObject {Id = 1})) *

Это должно вывести ссылку с URL-адресом "/ Controller / SomeAction / 1", указывающим на ActionMethod вдоль строк:

public Controller : Controller
{
  public ActionResult SomeMethod(MessageObject message)
  {
      // do something with the message
      return View();
  }
}

Я написал нечто похожее для генерации форм, но нет необходимости включать значение Id в конце URL-адреса. По сути, я хочу сделать какой-то обратный поиск в моих маршрутах, но я не могу найти никаких документов о том, как я могу это сделать. У меня есть установка ModelBinder, которая способна создать MessageObject из параметров GET и POST, но я не уверен, как можно повернуть процесс вспять.

Спасибо, Matt

Ответы [ 4 ]

1 голос
/ 17 декабря 2008

В итоге я обернул следующий код в метод расширения HtmlHelper. Это позволило бы мне использовать что-то вроде Html.ActionLink (c => c.SomeAction (новый MessageObject {Id = 1}))

и все свойства объекта MessageObject созданы как RouteValues.

 public static RouteValueDictionary GetRouteValuesFromExpression<TController>(Expression<Action<TController>> action)
            where TController : Controller
        {
            Guard.Against<ArgumentNullException>(action == null, @"Action passed to GetRouteValuesFromExpression cannot be null.");
            MethodCallExpression methodCall = action.Body as MethodCallExpression;
            Guard.Against<InvalidOperationException>(methodCall == null, @"Action passed to GetRouteValuesFromExpression must be method call");
            string controllerName = typeof(TController).Name;
            Guard.Against<InvalidOperationException>(!controllerName.EndsWith("Controller"), @"Controller passed to GetRouteValuesFromExpression is incorrect");

            RouteValueDictionary rvd = new RouteValueDictionary();
            rvd.Add("Controller", controllerName.Substring(0, controllerName.Length - "Controller".Length));
            rvd.Add("Action", methodCall.Method.Name);

            AddParameterValuesFromExpressionToDictionary(rvd, methodCall);
            return rvd;
        }

        /// <summary>
        /// Adds a route value for each parameter in the passed in expression.  If the parameter is primitive it just uses its name and value
        /// if not, it creates a route value for each property on the object with the property's name and value.
        /// </summary>
        /// <param name="routeValues"></param>
        /// <param name="methodCall"></param>
        private static void AddParameterValuesFromExpressionToDictionary(RouteValueDictionary routeValues, MethodCallExpression methodCall)
        {
            ParameterInfo[] parameters = methodCall.Method.GetParameters();
            methodCall.Arguments.Each(argument =>
            {
                int index = methodCall.Arguments.IndexOf(argument);

                ConstantExpression constExpression = argument as ConstantExpression;
                if (constExpression != null)
                {
                    object value = constExpression.Value;
                    routeValues.Add(parameters[index].Name, value);
                }
                else
                {
                    object actualArgument = argument;
                    MemberInitExpression expression = argument as MemberInitExpression;
                    if (expression != null)
                    {
                        actualArgument = Expression.Lambda(argument).Compile().DynamicInvoke();
                    }

                    // create a route value for each property on the object
                    foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(actualArgument))
                    {
                        object obj2 = descriptor.GetValue(actualArgument);
                        routeValues.Add(descriptor.Name, obj2);
                    }
                }
            });
        }
0 голосов
/ 08 февраля 2010

Если вы не возражаете добавить метод рядом с каждым действием в вашем контроллере, для которого вы хотите сгенерировать URL, вы можете действовать следующим образом. Это имеет некоторые недостатки по сравнению с вашим подходом к лямбда-выражению, но есть и недостатки.

Реализация: -

Добавьте это в метод Controller for EACH, для которого вы хотите создать строго типизированный URL-адрес ...

// This const is only needed if the route isn't already mapped 
// by some more general purpose route (e.g. {controller}/{action}/{message}
public const string SomeMethodUrl = "/Home/SomeMethod/{message}";

// This method generates route values that match the SomeMethod method signature
// You can add default values here too
public static object SomeMethodRouteValues(MessageObject messageObject)
{
   return new { controller = "Home", action = "SomeMethod", 
                message = messageObject };
} 

Вы можете использовать их в своем коде отображения маршрута ...

Routes.MapRoute ("SomeMethod", 
                  HomeController.SomeMethodUrl,
                  HomeController.SomeMethodRouteValues(null));

И вы можете использовать их ВЕЗДЕ, где вам нужно создать ссылку на это действие: - например,

<%=Url.RouteUrl(HomeController.SomeMethodValues(new MessageObject())) %>

Если вы сделаете это таким образом ...

1) В вашем коде есть только одно место, где определены параметры для любого действия

2) Существует только один способ преобразования этих параметров в маршруты, поскольку Html.RouteLink и Url.RouteUrl могут оба принять HomeController.SomeMethodRouteValues ​​(...) в качестве параметра.

3) Легко установить значения по умолчанию для любых дополнительных значений маршрута.

4) Рефакторинг вашего кода легко, не нарушая URL. Предположим, вам нужно добавить параметр в SomeMethod. Все, что вы делаете, это изменяете SomeMethodUrl и SomeMethodRouteValues ​​(), чтобы они соответствовали новому списку параметров, и затем вы исправляете все сломанные ссылки в коде или в представлениях. Попробуйте сделать это с новым {action = "SomeMethod", ...} разбросанным по всему коду.

5) Вы получаете поддержку Intellisense, чтобы вы могли ВИДЕТЬ, какие параметры необходимы для создания ссылки или ссылки на какое-либо действие. С точки зрения «строгой типизации», этот подход кажется лучше, чем использование лямбда-выражений, где нет проверки времени компиляции или ошибки разработки, чтобы убедиться, что ваши параметры генерации ссылки действительны.

Недостатком является то, что вам по-прежнему необходимо синхронизировать эти методы с фактическим методом действия (но они могут быть рядом друг с другом в коде, что облегчает просмотр). Пуристы, без сомнения, будут возражать против этого подхода, но практически говорят о том, что он находит и исправляет ошибки, которые в противном случае потребовали бы тестирования, чтобы найти, и он помогает заменить строго типизированные методы Page, которые мы использовали в наших проектах WebForms.

0 голосов
/ 16 декабря 2008

Я не совсем уверен, кто ты пытаясь сделать, так как ваш пример URL не соответствует тому, что требуется для подпись вашего метода. Как правило, если вы используете метод, который требует сложный объект, вы передаете значения построить этот объект в запросе строка или как параметры формы и ModelBinder создает объект из данные, представленные в параметрах.

LOL, это именно то, что я пытаюсь сделать :) Этот URL-адрес работает нормально и отображается в соответствии с этим методом, механизм связывания моделей может превратить этот URL-адрес в маршрут, который отображается в этом действии и работает нормально. (Этот маршрут отображает «1» в RouteValue с именем Id, который связыватель модели затем назначает полю Id объекта сообщения).

Я пытаюсь пойти другим путем, взять вызов метода и превратить его в маршрут.

0 голосов
/ 16 декабря 2008

Я не уверен, что именно вы пытаетесь сделать, так как ваш пример URL не совпадает с тем, что требуется для подписи вашего метода. Как правило, если вы используете метод, который требует сложного объекта, вы передаете значения для создания этого объекта в строке запроса или в качестве параметров формы, а ModelBinder создает объект из данных, предоставленных в параметрах. Если вы хотите передать только идентификатор, метод обычно не принимает никаких параметров, вы извлекаете идентификатор из RouteData и ищите объект в постоянном хранилище (или в кэше). Если вы хотите сделать последнее, ваш метод должен выглядеть следующим образом:

public ActionResult SomeMethod()
{
    int messageObjectID;
    if (RouteData.Values.TryGetValue("id",out messageObjectID))
    {
       ... get the object with the correct id and process it...
    }
    else
    {
       ... error processing because the id was not available...
    }
    return View();
}
...