MVC ActionLink add all (optional) parameters from current url
Asked Answered
S

5

20

The very famous ActionLink:

 <%: Html.ActionLink("Back to List", "Index")%>

Now, this link is in my Details view. The Index view is a search page. The URL of that looks like this:

http://localhost:50152/2011-2012/Instelling/Details/76?gemeente=Dendermonde&postcode=92**&gebruikerscode=VVKSO114421&dossiernr=114421%20&organisatie=CLB

As you can see, quite the amount of parameters. Obviously I want to keep all these parameters when I return to the Index page, so I need to add them in the ActionLink.

Now, I'm tired of doing that manually, it's ok for 1, but not for 6. This should go a lot easier.

Question: How do I return All parameters of the current URL into the ActionLink as optional RouteValues.

I've been looking to Request.QueryString. It has to be something with that. I was thinking of writing some static method in Global.asax doing the job but no luck yet. Maybe there is an easy way to do this which I don't know about?

Edit: This is what I came up with (which works)

In global.asax:

    public static RouteValueDictionary optionalParamters(NameValueCollection c) {
        RouteValueDictionary r = new RouteValueDictionary();
        foreach (string s in c.AllKeys) {
            r.Add(s, c[s]);
        }
        return r;
    }

Details.aspx:

    <%: Html.ActionLink("Back to List", "Index", MVC2_NASTEST.MvcApplication.optionalParamters(Request.QueryString))%>

Where do I best put this code? not in Global.asax I guess...

Edit 2:

using System;
using System.Web.Mvc;

namespace MVC2_NASTEST.Helpers {
    public static class ActionLinkwParamsExtensions {
        public static MvcHtmlString CustomLink(this HtmlHelper helper, string linktext) {
            //here u can use helper to get View context and then routvalue dictionary
            var routevals = helper.ViewContext.RouteData.Values;
            //here u can do whatever u want with route values
            return null;
        }

    }
}


<%@ Import Namespace="MVC2_NASTEST.Helpers" %>
...
<%: Html.ActionLinkwParams("Index") %>
Sudbury answered 23/9, 2010 at 15:29 Comment(0)
S
21

This is how I finally fixed it, and i'm rather proud because it's working very well and very DRY.

The call in the View:

    <%: Html.ActionLinkwParams("Back to List", "Index")%>

but with the overloads it can be anything which a normal ActionLink takes.

The Helper:

The helper takes all parameters from the url which are not in the route. For example: this url:

http://localhost:50152/2011-2012/myController/Details/77?postalCode=9***&org=CLB

So it will take the postalCode and the Org and place it in the new ActionLink. With the overload, additional parameters can be added, and parameters from the existing url can be removed.

using System;
using System.Web.Mvc;
using System.Web.Routing;
using System.Collections.Specialized;
using System.Collections.Generic;

namespace MVC2_NASTEST.Helpers {
    public static class ActionLinkwParamsExtensions {
        public static MvcHtmlString ActionLinkwParams(this HtmlHelper helper, string linktext, string action, string controller, object extraRVs, object htmlAttributes) {

            NameValueCollection c = helper.ViewContext.RequestContext.HttpContext.Request.QueryString;

            RouteValueDictionary r = new RouteValueDictionary();
            foreach (string s in c.AllKeys) {
                r.Add(s, c[s]);
            }

            RouteValueDictionary htmlAtts = new RouteValueDictionary(htmlAttributes);

            RouteValueDictionary extra = new RouteValueDictionary(extraRVs);

            RouteValueDictionary m = Merge(r, extra);

            return System.Web.Mvc.Html.LinkExtensions.ActionLink(helper, linktext, action, controller, m, htmlAtts);
        }

        public static MvcHtmlString ActionLinkwParams(this HtmlHelper helper, string linktext, string action) {
            return ActionLinkwParams(helper, linktext, action, null, null, null);
        }

        public static MvcHtmlString ActionLinkwParams(this HtmlHelper helper, string linktext, string action, string controller) {
            return ActionLinkwParams(helper, linktext, action, controller, null, null);
        }

        public static MvcHtmlString ActionLinkwParams(this HtmlHelper helper, string linktext, string action, object extraRVs) {
            return ActionLinkwParams(helper, linktext, action, null, extraRVs, null);
        }

        public static MvcHtmlString ActionLinkwParams(this HtmlHelper helper, string linktext, string action, string controller, object extraRVs) {
            return ActionLinkwParams(helper, linktext, action, controller, extraRVs, null);
        }

        public static MvcHtmlString ActionLinkwParams(this HtmlHelper helper, string linktext, string action, object extraRVs, object htmlAttributes) {
            return ActionLinkwParams(helper, linktext, action, null, extraRVs, htmlAttributes);
        }




        static RouteValueDictionary Merge(this RouteValueDictionary original, RouteValueDictionary @new) {

            // Create a new dictionary containing implicit and auto-generated values
            RouteValueDictionary merged = new RouteValueDictionary(original);

            foreach (var f in @new) {
                if (merged.ContainsKey(f.Key)) {
                    merged[f.Key] = f.Value;
                } else {
                    merged.Add(f.Key, f.Value);
                }
            }

            return merged;

        }
    }

}

In the View using overloads:

 <%: Html.ActionLinkwParams("Back to List", "Index","myController", new {testValue = "This is a test", postalCode=String.Empty}, new{ @class="test"})%>

in the URL I have the paramters postalCode with some value. my code takes All of them in the URL, by setting it to string.Empty, I remove this parameter from the list.

Comments or ideas welcome on optimizing it.

Sudbury answered 24/9, 2010 at 11:21 Comment(3)
Very useful for next/prev page functionality when you need to take the current query string. Cheers for this +1Jewelljewelle
This worked nicely for me (after I modified it a bit to rename some of the horrible variable names). +1!Jacklin
What a life saver and now I know how to create my own Html extension methods. two thumbs up!Seger
N
11

Create a ToRouteValueDictionary() extension method for Request.QueryString to use Html.ActionLink as-is and simplify your view markup:

<%: Html.ActionLink("Back to List", "Index", Request.QueryString.ToRouteValueDictionary())%>

Your extension method might look like this:

using System.Web.Routing;
using System.Collections.Specialized;

namespace MyProject.Extensions
{
    public static class CollectionExtensions
    {
        public static RouteValueDictionary ToRouteValueDictionary(this NameValueCollection collection)
        {
            var routeValueDictionary = new RouteValueDictionary();
            foreach (var key in collection.AllKeys)
            {
                routeValueDictionary.Add(key, collection[key]);
            }
            return routeValueDictionary;
        }
    }
}

To use the extension method in your view see this question and answer: How do I use an extension method in an ASP.NET MVC View?

This is simpler and involves much less code than the accepted answer.

Namecalling answered 7/4, 2011 at 15:5 Comment(2)
did you read the accepted code? it does the same you described, the same code, AND more. merging url routevalues and personal added routevalues, and so on. it works remarkably well i must say.Sudbury
Thanks for your comment, @Stefanvds. I did read the accepted answer. There's nothing wrong with it, but it adds behavior that was not in the question. Also, the question asked "where do I best put this code?" This answer offers the option of using an extension method.Namecalling
B
6

Here is an extension method for ViewContext that creates a RouteValueDictionary based on the request route values and querystring.

using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Web.Mvc;
using System.Web.Routing;

namespace MyMvcApplication.Utilities
{
    public static class ViewContextExtensions
    {
        /// <summary>
        /// Builds a RouteValueDictionary that combines the request route values, the querystring parameters,
        /// and the passed newRouteValues. Values from newRouteValues override request route values and querystring
        /// parameters having the same key.
        /// </summary>
        public static RouteValueDictionary GetCombinedRouteValues(this ViewContext viewContext, object newRouteValues)
        {
            RouteValueDictionary combinedRouteValues = new RouteValueDictionary(viewContext.RouteData.Values);

            NameValueCollection queryString = viewContext.RequestContext.HttpContext.Request.QueryString;
            foreach (string key in queryString.AllKeys.Where(key => key != null))
                combinedRouteValues[key] = queryString[key];

            if (newRouteValues != null)
            {
                foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(newRouteValues))
                    combinedRouteValues[descriptor.Name] = descriptor.GetValue(newRouteValues);
            }


            return combinedRouteValues;
        }
    }
}

You can pass the created RouteValueDictionary to Html.ActionLink or Url.Action

@Html.ActionLink("5", "Index", "Product",
    ViewContext.GetCombinedRouteValues(new { Page = 5 }),
    new Dictionary<string, object> { { "class", "page-link" } })

If the Page parameter does not exist in the request URL, it will be added in the generated URL. If it does exist, its value will be changed to 5.

This article has a more detailed explanation of my solution.

Broglie answered 25/3, 2012 at 2:46 Comment(0)
P
1
public static class Helpers
    {
        public static MvcHtmlString CustomLink(this HtmlHelper helper,string LinkText, string actionName)
        {
            var rtvals = helper.ViewContext.RouteData.Values;
            var rtvals2 = helper.RouteCollection;
            RouteValueDictionary rv = new RouteValueDictionary();
            foreach (string param in helper.ViewContext.RequestContext.HttpContext.Request.QueryString.AllKeys) 
            {
                rv.Add(param, helper.ViewContext.RequestContext.HttpContext.Request.QueryString[param]);
            }
            foreach (var k in helper.ViewContext.RouteData.Values) 
            {
                rv.Add(k.Key, k.Value);
            }
            return helper.ActionLink(LinkText, actionName, rv);
        }
    }

i have tested this and its working. optional parameters can be acquired from query string HTH

Poirier answered 24/9, 2010 at 16:13 Comment(0)
P
0

Perhaps the best way is to write your own html helper where you traverse through the previous route value dictionary and add route values to the current action link, except the action parameter off course.

edit: You can write the html helper like this:

public static MvcHtmlString CustomLink(this HtmlHelper helper,string linktext) 
{
    //here you can use helper to get View context and then routvalue dictionary
    var routevals = helper.ViewContext.RouteData.Values;
    //here you can do whatever you want with route values
}
Poirier answered 23/9, 2010 at 15:34 Comment(8)
RouteValueDictionary is exactly what I needed. But how would you make that helper then?Sudbury
@Stefanvds: You would call it with <%= Html.CustomLink("Foo") %>. P.S. @Muhammad - What's with all the txt spk? We're not constrained to 160 chars here, you know!Agathy
I need more info on this thing, I dont get it. some articles could be helpful. error: 'System.Web.Mvc.HtmlHelper<MVC2_NASTEST.Models.DetailedInstelling>' does not contain a definition for 'ActionLinkwParams' and no extension method 'ActionLinkwParams' accepting a first argument of type 'System.Web.Mvc.HtmlHelper<MVC2_NASTEST.Models.DetailedInstelling>' could be found (are you missing a using directive or an assembly reference?Sudbury
ah sorry plz use string in place of MvcHtmlStringPoirier
plus u need to add namespace in ur veiw to use custom html helpers plz c #1105776 for more detailPoirier
i did import it. the problem is with this HtmlHelper.Sudbury
helper.ViewContext.RouteData.Values is useless, it contains the values from my route, obviously the optional paramters are not in my route... so they are not there!Sudbury
sorry today was too busy i have written some code adding in new answerPoirier

© 2022 - 2024 — McMap. All rights reserved.