I'm implementing a site which has a search that can be executed via Controller methods that are accessed from two different routes. One hangs off the default route (using either Post data or query string values for the search parameters) and one is an SEO optimisation url which takes a location and phrase via two route values. The second route looks like this:
routes.MapRoute("SEOSearch", "Search/{seoLocation}/{seoSearchString}",
new { controller = "Search",
action = "SEOResults",
seoLocation = (string)null,
seoSearchString = (string)null });
You might be wondering why I have two different routes - it's because the search offers many other parameters than just location and phrase - but I want the SEO'd urls to include those two in the path, rather than using the query string.
As I say, the first route is the default /controller/action/id
route, and the correct controller/action for that is "Search" and "Index".
In the end, both actions execute the same search operation in the controller, and both will render their results using the Index view, since their result models are identical.
On the index view I use a partial view for the search terms, another partial for the results and another for the paging.
The problem I'm having is getting the paging control to render the correct link to launch the current search for the next page using the same URL format as the current request.
What I want
So, assuming you've navigated to /Search?Location=[location]&Phrase=[phrase]
, I want Page 2's link generated by the pager to be /Search?Location=[location]&Phrase=[phrase]&Page=2
.
However, if you've launched the search with /Search/[location]/[phrase]
, I want Page 2's link to be /Search/[location]/[phrase]?Page=2
.
What I've got
The closest I've got is this:
<%= Html.RouteLink("Previous Page",
RouteHelpers.Combine(ViewContext.RouteData.Values,
new RouteValueDictionary() { { "Page", Model.Results.PageNo + 1}})) %>
Where RouteHelpers.Combine
is an extension that I've written that takes two objects and merges them into one RouteValueDictionary. By taking the RouteValues for the current request, I'm able to persist the current Controller and Action name (without having to know what they are) - however this misses some important information from ModelState - i.e. any extra search parameters that were provided - i.e. it works if the current Url is /Search/London/Widgets
, but if it's /Search/London/Widgets?PageSize=50
then the PageSize
parameter doesn't get persisted into the outgoing link.
Even worse, if it's a non-SEO'd url - i.e. /Search?Location=London&Phrase=Widgets
, the outgoing url simply becomes /Search?Page=x
.
My search parameters are read from the request into a model type, that is then fed to both the host page, and to the pager itself, so in theory I could simply always generate them from that - but I end up with all the parameters in the url, even when they are default values (therefore they do not need to be supplied) - so the url looks ugly.
How do I achieve what I want!? I'm feeling like I know nothing about MVC all of sudden!