从使用当前路由值和模型状态的分页用户控件生成 ActionLink/RouteLink

发布于 2024-08-25 06:55:09 字数 1899 浏览 3 评论 0原文

我正在实现一个站点,该站点具有可以通过从两个不同路径访问的控制器方法执行的搜索。一种是挂在默认路由上(使用发布数据或查询字符串值作为搜索参数),另一种是 SEO 优化 URL,它通过两个路由值获取位置和短语。第二条路线如下所示:

routes.MapRoute("SEOSearch", "Search/{seoLocation}/{seoSearchString}",
  new { controller = "Search", 
        action = "SEOResults", 
        seoLocation = (string)null, 
        seoSearchString = (string)null });

您可能想知道为什么我有两条不同的路线 - 这是因为搜索提供了许多其他参数,而不仅仅是位置和短语 - 但我希望 SEO 网址在路径中包含这两个,而不是使用查询字符串。

正如我所说,第一个路由是默认的 /controller/action/id 路由,正确的控制器/操作是“Search”和“Index”。

最后,两个操作在控制器中执行相同的搜索操作,并且都将使用索引视图呈现其结果,因为它们的结果模型是相同的。

在索引视图上,我使用一个部分视图来显示搜索词,另一个部分视图用于结果,另一个部分视图用于分页。

我遇到的问题是让分页控件呈现正确的链接,以使用与当前请求相同的 URL 格式启动对下一页的当前搜索。

我想要什么

那么,假设您已导航到 /Search?Location=[location]&Phrase=[phrase],我希望寻呼机生成的第 2 页的链接为 /Search ?Location=[位置]&Phrase=[phrase]&Page=2

但是,如果您使用 /Search/[location]/[phrase] 启动搜索,我希望第 2 页的链接为 /Search/[location]/[phrase]?Page =2

我得到的

最接近的是:

<%=  Html.RouteLink("Previous Page", 
       RouteHelpers.Combine(ViewContext.RouteData.Values, 
       new RouteValueDictionary() { { "Page", Model.Results.PageNo + 1}})) %>

其中 RouteHelpers.Combine 是我编写的一个扩展,它接受两个对象并将它们合并到一个 RouteValueDictionary 中。通过获取当前请求的 RouteValues,我能够保留当前的控制器和操作名称(无需知道它们是什么) - 但是这会错过来自 ModelState 的一些重要信息 - 即提供的任何额外搜索参数 - 即如果当前网址为 /Search/London/Widgets,则有效,但如果为 /Search/London/Widgets?PageSize=50,则 PageSize > 参数不会保留到传出链接中。

更糟糕的是,如果它是非 SEO 网址 - 即 /Search?Location=London&Phrase=Widgets,则传出网址将简单地变为 /Search?Page=x

我的搜索参数从请求中读取到模型类型中,然后将其馈送到主机页面和寻呼机本身,因此理论上我可以简单地始终从中生成它们 - 但我最终得到了所有参数url,即使它们是默认值(因此不需要提供它们) - 所以 url 看起来很难看。

我怎样才能达到我想要的目标!?我突然感觉自己对MVC一无所知!

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!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

鯉魚旗 2024-09-01 06:55:09

您不必使用 RouteHelper 来组合路由值。当您使用 ActionLink 时,这些值会自动与您的匿名对象组合。所有现有值将被覆盖并添加新值。此调用会为您执行此操作:

RouteValueDictionary values = RouteValuesHelpers.MergeRouteValues(
    actionName,
    controllerName,
    requestContext.RouteData.Values,
    routeValues,
    includeImplicitMvcValues); // true for ActionLink; false for RouteLink

其中 routeValues 是来自匿名对象的值。它们被合并到requestContext.RouteData.Values

所以你仍然可以使用:

Html.ActionLink(
    "whatever",
    this.ViewContext.RouteData.Values["action"],
    this.ViewContext.RouteData.Values["controller"],
    new { Page = /* whatever needs to be */ },
    null)

末尾的 Null 是强制的,这样就不会混淆匿名对象和 HTML 属性。

但是您确实存在路由问题,除非您还在默认路由上设置了路由限制以仅使用某些操作,否则您的 /Search/SeoLocation/SeoSearchString 仍将由您的默认路由处理,即 SeoLocation 成为您的操作,SeoSearchString 成为id

反正。如果您的代码正确,所有这些值都应该位于您的路由值字典内,并按预期传播到您的链接。

You don't have to use your RouteHelper to combine route values. Those values are combined with your anonymous object automatically when you use ActionLink instead. All existing values will be overwritten and new ones added. This call does that for you:

RouteValueDictionary values = RouteValuesHelpers.MergeRouteValues(
    actionName,
    controllerName,
    requestContext.RouteData.Values,
    routeValues,
    includeImplicitMvcValues); // true for ActionLink; false for RouteLink

Where routeValues are your values from anonymous object. They get merged to requestContext.RouteData.Values.

So you can still use:

Html.ActionLink(
    "whatever",
    this.ViewContext.RouteData.Values["action"],
    this.ViewContext.RouteData.Values["controller"],
    new { Page = /* whatever needs to be */ },
    null)

Null at the end is mandatory, so it doesn't confuse anonymous object with HTML attributes.

But you do have routing problems unless you also put a route constraint on your default route to only use certain actions, otherwise your /Search/SeoLocation/SeoSearchString will still be handled by your default route, by SeoLocation becoming your action and SeoSearchString the id.

Anyway. If your code would be correct all those values should be inside your route values dictionary and propagated to your links as expected.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文