如何使用视图模型的属性创建ActionLink

时间:2012-04-04 08:51:28

标签: asp.net-mvc asp.net-mvc-3 model-binding actionlink

我有一个带有Filter属性的ViewModel,它有许多属性我用来过滤我的数据

示例:

class MyViewModel : IHasFilter
{
     public MyData[] Data { get; set; }
     public FilterViewModel Filter { get; set; }
}

class FilterViewModel
{
    public String MessageFilter { get; set; }
    //etc.
}

使用我的视图时这很好用。我可以设置Model.Filter的属性,然后将它们传递给Controller。我现在要做的是创建一个ActionLink,其中包含一个符合上述格式的查询字符串。

我的View从上面生成的查询字符串如下所示:

http://localhost:51050/?Filter.MessageFilter=Stuff&Filter.OtherProp=MoreStuff

我需要在网格中每行进入上面视图的不同视图中生成一个ActionLink。

我试过了:

Html.ActionLink(
    item.Message,
    "Index",
    "Home",
    new { Filter = new { MessageFilter = item.Message, }, },
    null);

我也尝试将routeValues参数设置为:

new MyViewModel { Filter = new FilterViewModel { MessageFilter = item.Message, }, },

但是这些不会像上面那样生成查询字符串。

2 个答案:

答案 0 :(得分:2)

有趣的问题(+1)。我假设目的是使用默认模型绑定器将查询字符串参数绑定到您的Action参数。

开箱即用我不相信ActionLink方法会为你做这件事(当然没有什么能阻止你自己动手)。查看反射器,我们可以看到,object添加到RouteValueDictionary时,只会添加键值对。这是添加键值对的代码,您可以看到没有遍历对象属性。

foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(values))
{
    object obj2 = descriptor.GetValue(values);
    this.Add(descriptor.Name, obj2);
}

所以对你的对象

var values = new { Filter = new Filter { MessageFilter = item.Message } }

要添加的密钥为Filter,值为您的Filter对象,该对象将评估为对象类型的完全限定名称。

结果是Filter=Youre.Namespace.Filter

根据您的具体需求修改 可能的解决方案


扩展方法 完成工作

请注意,它使用静态框架方法ExpressionHelperModelMetadata(也由现有帮助程序使用)来确定默认模型绑定器将理解的适当名称和值属性分别。

public static class ExtentionMethods
{
    public static MvcHtmlString ActionLink<TModel, TProperty>(
        this HtmlHelper<TModel> helper,
        string linkText,
        string actionName,
        string controllerName,
        params Expression<Func<TModel, TProperty>>[] expressions)
    {
        var urlHelper = new UrlHelper(helper.ViewContext.HttpContext.Request.RequestContext);

        var url = urlHelper.Action(actionName, controllerName);

        if (expressions.Any())
        {
            url += "?";

            foreach (var expression in expressions)
            {
                var result = ExpressionHelper.GetExpressionText(expression);

                var metadata = ModelMetadata.FromLambdaExpression<TModel, TProperty>(expression, helper.ViewData);

                url = string.Concat(url, result, "=", metadata.SimpleDisplayText, "&");
            }

            url = url.TrimEnd('&');
        }

        return new MvcHtmlString(string.Format("<a href='{0}'>{1}</a>", url, linkText));
    }
}

样本模型

public class MyViewModel
{
    public string SomeProperty { get; set; }

    public FilterViewModel Filter { get; set; }
}

public class FilterViewModel
{
    public string MessageFilter { get; set; }
}

<强>动作

public ActionResult YourAction(MyViewModel model)
{
    return this.View(
        new MyViewModel
        {
            SomeProperty = "property value",
            Filter = new FilterViewModel
            {
                MessageFilter = "stuff"
            }
        });
}

<强>用法

可以通过方法的最后params参数将任意数量的视图模型属性添加到查询字符串中。

@this.Html.ActionLink(
    "Your Link Text",
    "YourAction",
    "YourController",
    x => x.SomeProperty,
    x => x.Filter.MessageFilter)

<强>标记

<a href='/YourAction/YourController?SomeProperty=some property value&Filter.MessageFilter=stuff'>Your Link Text</a>

不是使用string.Format而是使用TagBuilder,而是应该对查询字符串进行编码以便在URL中安全传递,并且此扩展方法需要一些额外的验证,但我认为它可能是有用。另请注意,虽然此扩展方法是为MVC 4构建的,但可以轻松修改以前的版本。我没有意识到其中一个MVC标签是针对版本3的。

答案 1 :(得分:1)

您可以从FilterViewModel实例创建一个RouteValueDictionary,然后使用ToDictionary传递给另一个RouteValues,其中所有键都以'Filter.'为前缀。

进一步说,你可以构造一个RouteValueDictionary的特殊覆盖,它接受一个前缀(因此它对其他场景更有用):

public class PrefixedRouteValueDictionary : RouteValueDictionary
{
  public PrefixedRouteValueDictionary(string prefix, object o)
    : this(prefix, new RouteValueDictionary(o))
  { }

  public PrefixedRouteValueDictionary(string prefix, IDictionary<string, object> d)
    : base(d.ToDictionary(kvp=>(prefix ?? "") + kvp.Key, kvp => kvp.Value))
  { }
}

现在你可以这样做:

Html.ActionLink( 
  item.Message, 
  "Index", 
  "Home", 
  new PrefixedRouteValueDictionary("Filter.", 
    new FilterViewModel() { MessageFilter = item.Message }), 
  null); 

但需要注意的是,AddRemoveTryGetValuethis[string key]方法不会被更改以考虑{{1} }。这可以通过定义这些方法的prefix版本来实现,但由于它们不是虚拟的,它们只能从知道他们正在与new而不是{{1}交谈的呼叫者那里工作。 }}