1

有谁知道我该怎么做:

Html.ActionLink(c => c.SomeAction(new MessageObject { Id = 1 } ))

这应该输出一个带有“/Controller/SomeAction/1”的url的链接,指向一个ActionMethod,如下所示:

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

我为生成表单编写了类似的内容,但不需要在 Url 的末尾包含 Id 值。基本上我想在我的路线中进行某种反向查找,但我找不到任何关于如何去做的文档。我有一个能够从 GET 和 POST 参数构建 MessageObject 的 ModelBinder 设置,但我不确定如何反转该过程。

谢谢,马特

4

4 回答 4

1

最后,我最终将以下代码包装在 HtmlHelper 扩展方法中。这将允许我使用类似 Html.ActionLink(c => c.SomeAction(new 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);
                    }
                }
            });
        }
于 2008-12-17T11:32:47.230 回答
0

我不确定您到底要做什么,因为您的示例 URL 与您的方法签名所需的不匹配。通常,如果您使用需要复杂对象的方法,则将值传递到查询字符串中或作为表单参数构造该对象,然后 ModelBinder 从参数中提供的数据构造对象。如果您只想传递 id,则该方法通常不带任何参数,您从 RouteData 中提取 id,并在持久存储(或缓存)中查找对象。如果你想做后者,你的方法应该是这样的:

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();
}
于 2008-12-16T03:22:01.217 回答
0

我不确定您到底要做什么,因为您的示例 URL 与您的方法签名所需的不匹配。通常,如果您使用需要复杂对象的方法,则将值传递到查询字符串中或作为表单参数构造该对象,然后 ModelBinder 从参数中提供的数据构造对象。

大声笑,这正是我想要做的 :) 该 url 工作正常并映射到该方法,模型绑定器能够将该 URL 转换为映射到该操作的路由并且工作正常。(该路由将“1”映射到名为 Id 的 RouteValue,然后模型绑定器将其分配给消息对象的 Id 字段)。

我正在尝试做的是另一种方式,进行方法调用并将其转换为路线。

于 2008-12-16T10:52:07.733 回答
0

如果您不介意在您要为其生成 URL 的控制器中的每个操作旁边添加一个方法,您可以按照以下步骤进行操作。与您的 lambda 表达式方法相比,这有一些缺点,但也有一些优点。

执行:-

将此添加到您的控制器中,以获取您想要为其生成强类型 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() 以匹配新参数列表,然后修复所有损坏的引用,无论是在代码中还是在视图中。尝试使用散布在您的代码中的 new {action="SomeMethod", ...} 来执行此操作。

5) 您获得 Intellisense 支持,因此您可以查看构建任何操作的链接或 URL 所需的参数。就“强类型”而言,这种方法似乎比使用没有编译时或设计时错误检查链接生成参数有效的 lambda 表达式更好。

缺点是您仍然必须使这些方法与实际操作方法保持同步(但它们可以在代码中彼此相邻,以便于查看)。纯粹主义者无疑会反对这种方法,但实际上它是在查找和修复原本需要测试才能发现的错误,并且它有助于替换我们过去在 WebForms 项目中使用的强类型 Page 方法。

于 2010-02-08T18:54:19.680 回答