Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create an ActionLink with Properties for the 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 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.

like image 340
DaveShaw Avatar asked Apr 04 '12 08:04

DaveShaw


People also ask

How do I pass model value in ActionLink?

ActionLink is rendered as an HTML Anchor Tag (HyperLink) and hence it produces a GET request to the Controller's Action method which cannot be used to send Model data (object). Hence in order to pass (send) Model data (object) from View to Controller using @Html.

What are the parameters for HTML ActionLink?

ActionLink takes at least two parameters as Html. ActionLink(LinkText, ActionMethod).

What is the difference between using url action and HTML ActionLink in a view?

Yes, there is a difference. Html. ActionLink generates an <a href=".."></a> tag whereas Url. Action returns only an url.

How can we navigate from one view to another using a HyperLink?

We while creating MVC application need to navigate from one View to another. The usual and simple way that we use is to use HTML elements "a" with its href attribute set to a specific controller's action, this is most obvious way of navigating right!.


1 Answers

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.

like image 101
11 revs Avatar answered Sep 17 '22 09:09

11 revs