How to create an actionlink using properties for a view model

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 great when using my view. I can set Model.Filter properties, and they are passed to the controller. Now I'm trying to create an ActionLink that has a query string that works with the above format.

The query string generated by my view above looks like this:

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

I need to create an ActionLink in a different view for each row in the grid that jumps to the view above.

I 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 they do not generate a query string, as shown above.

+6
source share
2 answers

You can create one RouteValueDictionary from an instance of FilterViewModel and then use ToDictionary to do this to go to another RouteValues ​​with all keys prefixed with 'Filter.' .

Taking it further, you can create a special RouteValueDictionary override that takes a prefix (therefore, makes it more useful for other scripts):

 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 this you can now:

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

However, it should be noted that the Add , Remove , TryGetValue and this[string key] methods are not changed to take into account prefix . This can be achieved by defining new versions of these methods, but since they are not virtual, they will only work from subscribers who know that they are talking to PrefixedRouteValueDictionary instead of RouteValueDictionary .

+1
source

Interesting question (+1). I assume that the goal is to use the default middleware to bind the request parameters 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 that prevents you from skating on your own). Looking at the reflector, we see that when an object added to a RouteValueDictionary , only pairs of key values ​​are added. This is code that adds key value pairs, and, as you can see, there is no intersection of 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 Filter key to add, and this is your Filter object, which will evaluate the full name of your object type.

The result of this is Filter=Youre.Namespace.Filter .

Change the solution to your specific needs.


The extension method does the job

Note that it uses the ExpressionHelper and ModelMetadata static structure ModelMetadata (which are also used by existing helpers) to determine the appropriate names that will understand the default binder and property value, 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)); } } 

Model Examples

 public class MyViewModel { public string SomeProperty { get; set; } public FilterViewModel Filter { get; set; } } public class FilterViewModel { public string MessageFilter { get; set; } } 

Act

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

Using

Any number of your view model properties can be added to the queue using the last parameter of the params 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 can use TagBuilder , querystring should be encoded to pass the URL safely, and this extension method will require additional verification, but I think it might be useful. Also note that although this extension method was created for MVC 4, it can easily be changed for previous versions. I did not understand that one of the MVC tags was for version 3 so far.

+2
source

Source: https://habr.com/ru/post/912368/


All Articles