8

Say I have a page that display search results. I search for stackoverflow and it returns 5000 results, 10 per page. Now I find myself doing this when building links on that page:

<%=Html.ActionLink("Page 1", "Search", new { query=ViewData["query"], page etc..%>
<%=Html.ActionLink("Page 2", "Search", new { query=ViewData["query"], page etc..%>
<%=Html.ActionLink("Page 3", "Search", new { query=ViewData["query"], page etc..%>
<%=Html.ActionLink("Next", "Search", new { query=ViewData["query"], page etc..%>

I dont like this, I have to build my links with careful consideration to what was posted previously etc..

What I'd like to do is

<%=Html.BuildActionLinkUsingCurrentActionPostData
        ("Next", "Search", new { Page = 1});

where the anonymous dictionary overrides anything currently set by previous action.

Essentially I care about what the previous action parameters were, because I want to reuse, it sounds simple, but start adding sort and loads of advance search options and it starts getting messy.

Im probably missing something obvious

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501

8 Answers8

11

I had a similar problem inside an HtmlHelper; I wanted to generate links that linked backed to the current page, with a small adjustment in parameters (think incrementing the page number). So if I had URL /Item/?sort=Name&page=0, I wanted to be able to create links to the same page, but just change the page parameter, and have the sort parameter automatically included (ie /Item/?sort=Name&page=1).

My solution was this (for use in an HtmlHelper extension method, but since you can access the same data almost anywhere in MVC, you can adapt it easily to your uses):

private static RouteValueDictionary CreateRouteToCurrentPage(HtmlHelper html)
{
    RouteValueDictionary routeValues 
         = new RouteValueDictionary(html.ViewContext.RouteData.Values);

    NameValueCollection queryString 
         = html.ViewContext.HttpContext.Request.QueryString;

    foreach (string key in queryString.Cast<string>())
    {
        routeValues[key] = queryString[key];
    }

    return routeValues;
}

What the method does is take the RouteValueDictionary for the current request and create a copy of it. Then it adds each of the query parameters found in the query string to this route. It does this because, for some reason, the current request's RouteValueDictionary does not contain them (you'd think it would, but it doesn't).

You can then take the resultant dictionary, modify only a part of it, for example:

routeValues["page"] = 2;

and then give that dictionary to the out-of-the-box HtmlHelper methods for them to generate you a URL/etc.

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
Daniel Chambers
  • 1,645
  • 16
  • 25
1

Whenever you find yourself writing redundant code in your Views, write a helper. The helper could explicitly copy the parameters, as you're doing it now, or it could iterate the entire collection and copy automatically. If it were me, I would probably choose the former. Then you can just call your new helper, instead of rebuilding the parameters every time you make a link.

Craig Stuntz
  • 125,891
  • 12
  • 252
  • 273
1

I'm a little iffy as to what you are actually trying to do here. I think you are trying to automate the process of creating a list of links with only small changes between them. Apparently in your case the id number of "Page".

One way to do it, although possibly not the best is like so (My code makes use of a basic and contrived Product list and the ViewPage and PartialViewPage both use Strongly Typed Models):

On your ViewPage you would add code like this:

<div id="product_list">
        <% foreach (TestMVC.Product product in ViewData.Model)
           { %>
            <% Html.RenderPartial("ProductEntry", product); %>
        <% } %>
</div>

Your Partial View, in my case "ProductEntry", would then look like this:

<div class="product">
    <div class="product-name">
        <%= Html.ActionLink(ViewData.Model.ProductName, "Detail", new { id = ViewData.Model.id })%>
    </div> 
    <div class="product-desc">
        <%= ViewData.Model.ProductDescription %>
    </div>       
</div>

All I'm doing in that Partial View is consuming the model/viewdata that was passed from the parent view by the call to Html.RenderPartial

In your parent view you could modify a parameter on your model object before the call to Html.RenderPartial in order to set the specific value you are interested in.

Hope this helps.

Switters
  • 1,553
  • 2
  • 12
  • 13
1

Following helper method does just that :

    public static string EnhancedActionLink(this HtmlHelper helper, string linkText, string actionName, string controllerName, bool keepQueryStrings)
    {
        ViewContext context = helper.ViewContext;
        IDictionary<string, object> htmlAttributes = null;
        RouteValueDictionary routeValues = null;
        string actionLink = string.Empty;
        if (keepQueryStrings && context.RequestContext.HttpContext.Request.QueryString.Keys.Count > 0)
        {
            routeValues = new RouteValueDictionary(context.RouteData.Values);
            foreach (string key in context.RequestContext.HttpContext.Request.QueryString.Keys)
            {
                routeValues[key] = context.RequestContext.HttpContext.Request.QueryString[key];
            }
        }            
        actionLink = helper.ActionLink(linkText, actionName, controllerName, routeValues, htmlAttributes);
        return actionLink;
    }
Jalal El-Shaer
  • 14,502
  • 8
  • 45
  • 51
0

Here's the actionlink extension


  public static class ActionLinkExtension
    {
        public static MvcHtmlString ActionLinkWithQueryString(this HtmlHelper helper, string linkText, string action, string controller, object routeValues)
        {
            var context = helper.ViewContext;

            var currentRouteValues = new RouteValueDictionary(context.RouteData.Values);
            foreach (string key in context.HttpContext.Request.QueryString.Keys)
            {
                currentRouteValues[key] = context.HttpContext.Request.QueryString[key];
            }

            var newRouteValues = new RouteValueDictionary(routeValues);

            foreach (var route in newRouteValues)
            {
                if (!currentRouteValues.ContainsKey(route.Key))
                {
                    currentRouteValues.Add(route.Key, route.Value);
                }
                else
                {
                    currentRouteValues[route.Key] = route.Value;
                }
            }

            return helper.ActionLink(linkText, action, controller, currentRouteValues, null);
        }

    }

h3n
  • 5,142
  • 9
  • 46
  • 76
0

Not exactly an answer, but worth pointing out: If you want paging functionality, use the PagedList Nuget package (No need to re-invent the wheel). The following link provides a really nice example of how to use it: ASP.NET Tutorial

This is especially useful to you because query strings are saved in the URL when switching between pages.

Mr Jones
  • 1,188
  • 3
  • 17
  • 33
  • The user is asking a way to combine sorts and paging which that tutorial doesn't. It shows either or and not a way to combine them so you can get a, for example, sort order on page 2. Each time you select either a sort or a page, it resets the other. – Josh Feb 11 '14 at 08:59
0

take a look on this, it's a good example: http://nerddinnerbook.s3.amazonaws.com/Part8.htm

Florin Gugui
  • 3
  • 1
  • 2
  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. – Smamatti Dec 05 '12 at 18:55
0

After hours spent trying different solutions only this one worked for me: MVC ActionLink add all (optional) parameters from current url

Community
  • 1
  • 1
Ziad
  • 1,036
  • 2
  • 21
  • 31