当action方法不采用基本类型时,使用强类型ActionLink

时间:2008-12-15 17:25:32

标签: asp.net-mvc routing actionlink htmlextensions

有谁知道如何做以下事情:

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

这应输出一个带有“/ Controller / SomeAction / 1”网址的链接,指向一行ActionMethod:

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

我已经为生成表单编写了类似的东西,但是不需要在Url的末尾包含Id值。基本上我想在我的路线中进行某种反向查找,但我找不到任何关于如何做到这一点的doco。我有一个ModelBinder设置,能够从GET和POST参数构建一个MessageObject,但我不知道如何扭转这个过程。

谢谢, 马特

4 个答案:

答案 0 :(得分: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);
                    }
                }
            });
        }

答案 1 :(得分:0)

由于您的示例网址与方法签名所需的网址不匹配,因此我不确定您要执行的操作。通常,如果使用需要复杂对象的方法,则传递值以在查询字符串中构造该对象或作为表单参数,并且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();
}

答案 2 :(得分:0)

  

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

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

我要做的是走另一条路,接听方法并将其变成路线。

答案 3 :(得分:0)

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

实现: -

将此添加到您希望强类型网址生成的Controller for EACH操作方法...

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

您可以使用它们,无论您需要生成指向该操作的链接: - e.g。

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

如果你这样做......

1)您的代码中只有一个位置,其中定义了任何操作的参数

2)只有一种方法可以将这些参数转换为路由,因为Html.RouteLink和Url.RouteUrl都可以将HomeController.SomeMethodRouteValues(...)作为参数。

3)可以轻松为任何可选路线值设置默认值。

4)在不破坏任何网址的情况下,很容易重构代码。假设您需要向SomeMethod添加参数。您所做的就是更改SomeMethodUrl和SomeMethodRouteValues()以匹配新参数列表,然后修复所有损坏的引用,无论是在代码中还是在视图中。尝试将新的{action =“SomeMethod”,...}分散在您的代码中。

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

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