问题
I have a ViewModel with a Filter property that has many properties that I use to filter my data
Example:
class MyViewModel : IHasFilter
{
public MyData[] Data { get; set; }
public FilterViewModel Filter { get; set; }
}
class FilterViewModel
{
public String MessageFilter { get; set; }
//etc.
}
This works fine when using my View. I can set the properties of Model.Filter
and they are passed to the Controller. What I am trying to do now, is create an ActionLink
that has a query string that works with the above format.
The query string generated by my View from above looks like this:
http://localhost:51050/?Filter.MessageFilter=Stuff&Filter.OtherProp=MoreStuff
I need to generate an ActionLink in a different View for each row in a grid that goes to the View above.
I have tried:
Html.ActionLink(
item.Message,
"Index",
"Home",
new { Filter = new { MessageFilter = item.Message, }, },
null);
I also tried setting the routeValues
argument to:
new MyViewModel { Filter = new FilterViewModel { MessageFilter = item.Message, }, },
But these do not generate the query string like the above one.
回答1:
You could create one RouteValueDictionary from a FilterViewModel
instance and then use ToDictionary
on that to pass to another RouteValues with all the keys prefixed with 'Filter.'
.
Taking it further, you could construct a special override of RouteValueDictionary
which accepts a prefix (therefore making it more useful for other scenarios):
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))
{ }
}
With that you can now do:
Html.ActionLink(
item.Message,
"Index",
"Home",
new PrefixedRouteValueDictionary("Filter.",
new FilterViewModel() { MessageFilter = item.Message }),
null);
The caveat to this, though, is that the Add
, Remove
, TryGetValue
and this[string key]
methods aren't altered to take into account the prefix
. That can be achieved by defining new
versions of those methods, but because they're not virtual, they'd only work from callers that know they're talking to a PrefixedRouteValueDictionary
instead of a RouteValueDictionary
.
回答2:
Interesting question (+1). I'm assuming that the purpose is to use the default model binder to bind the querystring parameters to to your Action
parameters.
Out of the box I do not believe that the ActionLink
method will do this for you (of course there is nothing stopping you from rolling your own). Looking in reflector we can see that when the object
is added to the RouteValueDictionary
, only key value pairs are added. This is the code that adds the key value pairs and as you can see there is no traversing the object properties.
foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(values))
{
object obj2 = descriptor.GetValue(values);
this.Add(descriptor.Name, obj2);
}
So for your object
var values = new { Filter = new Filter { MessageFilter = item.Message } }
the key being added is Filter
and the value is your Filter
object which will evaluate to the the fully qualified name of your object type.
The result of this is Filter=Youre.Namespace.Filter
.
Edit possible solution depending on your exact needs
Extension Method does the work
Note that it uses the static framework methods ExpressionHelper
and ModelMetadata
(which are also used by the existing helpers) to determine the appropriate names that the default model binder will understand and value of the property respectively.
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));
}
}
Sample Models
public class MyViewModel
{
public string SomeProperty { get; set; }
public FilterViewModel Filter { get; set; }
}
public class FilterViewModel
{
public string MessageFilter { get; set; }
}
Action
public ActionResult YourAction(MyViewModel model)
{
return this.View(
new MyViewModel
{
SomeProperty = "property value",
Filter = new FilterViewModel
{
MessageFilter = "stuff"
}
});
}
Usage
Any number of your view model properties can be added to the querystring through that last params
parameter of the method.
@this.Html.ActionLink(
"Your Link Text",
"YourAction",
"YourController",
x => x.SomeProperty,
x => x.Filter.MessageFilter)
Markup
<a href='/YourAction/YourController?SomeProperty=some property value&Filter.MessageFilter=stuff'>Your Link Text</a>
Instead of using string.Format
you could use TagBuilder
, the querystring should be encoded to be safely passed in a URL and this extension method would need some additional validation but I think it could be useful. Note also that, though this extension method is built for MVC 4, it could be easily modified for previous versions. I didn't realize that that one of the MVC tags was was for version 3 until now.
来源:https://stackoverflow.com/questions/10007832/how-to-create-an-actionlink-with-properties-for-the-view-model