【问题标题】:Using a strongly typed ActionLink when the action method doesn't take a primitive type当操作方法不采用原始类型时使用强类型 ActionLink
【发布时间】:2008-12-15 17:25:32
【问题描述】:

有谁知道我该怎么做:

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 设置,但我不确定如何反转该过程。

谢谢, 马特

【问题讨论】:

    标签: asp.net-mvc routing actionlink htmlextensions


    【解决方案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);
                        }
                    }
                });
            }
    

    【讨论】:

      【解决方案2】:

      我不确定您到底要做什么,因为您的示例 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();
      }
      

      【讨论】:

        【解决方案3】:

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

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

        我想做的是另辟蹊径,进行方法调用并将其转换为路由。

        【讨论】:

          【解决方案4】:

          如果您不介意在您要为其生成 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() 以匹配新的参数列表,然后修复所有损坏的引用,无论是在代码中还是在视图中。尝试使用散布在您的代码中的新 {action="SomeMethod", ...} 来执行此操作。

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

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

          【讨论】:

            猜你喜欢
            • 2011-01-23
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-01-12
            • 1970-01-01
            • 2011-11-11
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多