重定向到以前访问过的URL

Redirect to a previously visited URL

本文关键字:URL 访问 重定向      更新时间:2023-09-26

我正在开发MVC 5 (asp)应用程序。我得到的要求之一是有多个导航路径指向同一个目标页面。问题是根据用户的导航历史记录返回到帖子后的上一页。让我们考虑一个有三个网页的基本场景

Customers/ShowAll ->显示所有客户的列表

Customers/Search ->根据搜索(姓名,国家…)显示客户列表

Customers/Update/1134 ->显示特定客户(即customer_id=1134)的更新页面

导航路径是这样的

ShowAll -> Update

Search -> Update

如果用户导航到"客户/搜索",然后"客户/更新/1134",更新客户信息并保存数据,我希望服务器重定向到页面"客户/搜索",因为这是用户使用的路径。这是一种非常基本的情况,但它可能更复杂,比如返回许多页面并总是返回以前访问过的页面(或者如果没有页面与历史记录匹配,则返回默认页面)。

我已经做了什么

我已经创建了一个原型,可以在服务器端跟踪用户的导航历史。它使用客户端的会话存储为当前浏览器选项卡提供唯一id。在每个页面卸载,它添加浏览器选项卡id到cookie。然后在服务器端有一个字典(在会话中),以选项卡id(从cookie中提取)作为键,并以访问过的url列表作为值。当前URL被添加到URL列表中。我发现这个解决方案是有效的,但它有一些缺陷。

  1. 如果JavaScript被禁用,这个解决方案将不起作用(这不是一个非常大的问题,因为我可以要求所有用户打开它(这是一个小公司的内部网))
  2. 如果一个选项卡是重复的,产生的两个选项卡将具有相同的id。这是由于会话存储的实现(至少在Chrome上)。因此,如果用户同时使用两个选项卡,服务器上的历史记录可能会损坏。
  3. 我将历史字典存储在会话变量中,因此如果会话超时,历史将丢失。我想把历史记录保存在数据库中,但我觉得对数据库来说有点过热。

最后一件事是url的字典被限制为最近访问的30页,因为我想限制服务器内存。这对我的问题不重要,但我觉得要提一下,因为我相信你们中的一些人可能会看到在历史记录中保留所有用户的所有页面的所有选项卡的问题。

我还想到了一个类似的解决方案,使用cookie传输每个请求访问的最后30个页面,并让服务器在需要时解析该历史。只有来自应用程序域的页面将被保留。这将解决会话超时后的持久化问题,但它会给服务器带来更多的处理,因为每个请求都会解析历史记录。我想知道是否有更好的解决方案,根据导航历史重定向用户。也许MVC 5中有一个我不知道的内置功能。

谢谢你的建议。

问候。

使用会话状态(正如您已经发现的)并不是一个很好的解决方案,因为:

  1. 超时,此时数据丢失。
  2. 如果用户没有以你期望的方式导航到页面(例如,通过Google SERP直接进入页面),那么它就不起作用。

使其100%工作的唯一方法是将所有导航标识符信息放入URL中,以便系统可以确定如何构建导航链接。

在MVC 5中没有内置的功能,但是你可以使用MvcSiteMapProvider来解决你的问题。它包含了菜单和SiteMapPath的HTML帮助器,它的作用就像一个面包屑路径。
@Html.MvcSiteMap().Menu()
@Html.MvcSiteMap().SiteMapPath()

它以不同的原理工作-它将单个共享节点层次(站点地图)加载到内存中。然后,传入的每个请求都匹配其中一个节点,并使用该映射来确定如何在HTML帮助程序中构建链接。没有使用会话状态

让它在您的场景中工作的技巧是使Customers/Update/1134页面在2个不同的url上可用。然后你可以配置2个不同的节点层次结构,它会根据路由信息知道匹配哪一个。

例如,您可以添加一个额外的路由值,指示您正在导航的页面是搜索页面。

@Html.ActionLink("Customer 1134", "Update", "Customers", new { source = "Search" }, null)

默认情况下,这将构建一个类似Customers/Update/1134?source=Search的URL。你可以通过调整你的路由配置来使它看起来更漂亮。

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
        routes.MapRoute(
            name: "SearchSource",
            url: "Search/{controller}/{action}/{id}",
            defaults: new { source = "Search", controller = "Home", action = "Index", id = UrlParameter.Optional }
        );
        routes.MapRoute(
            name: "ShowAllSource",
            url: "ShowAll/{controller}/{action}/{id}",
            defaults: new { source = "ShowAll", controller = "Home", action = "Index", id = UrlParameter.Optional }
        );
        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );
    }
}

现在与上面显示的相同的ActionLink,你会得到URL /Search/Customers/Update/1134。这是更好的。注意,当您将ActionLink放到ShowAll页面上时,它应该是这样的:

@Html.ActionLink("Customer 1134", "Update", "Customers", new { source = "ShowAll" }, null)

然后当你在MvcSiteMapProvider中设置节点配置时,你需要创建2个不同的父节点,像这样。

<mvcSiteMapNode title="Home" controller="Home" action="Index">
    <!-- Additional nodes here -->
    <mvcSiteMapNode title="Search" controller="Customers" action="Search">
        <mvcSiteMapNode title="Update Customer" controller="Customers" action="Update" source="Search" preservedRouteParameters="id"/>
    </mvcSiteMapNode>
    <mvcSiteMapNode title="Show All Customers" controller="Customers" action="ShowAll">
        <mvcSiteMapNode title="Update Customer" controller="Customers" action="Update" source="ShowAll" preservedRouteParameters="id"/>
    </mvcSiteMapNode>
    <!-- Additional nodes here -->
</mvcSiteMapNode>

你将得到一个完整的导航解决方案:

/Customers/ShowAll                  | Home > Show All Customers
/ShowAll/Customers/Update/1134      | Home > Show All Customers > Update Customer
/Customers/Search                   | Home > Search
/Search/Customers/Update/1134       | Home > Search > Update Customer

当然,这只是一个例子。你可以让url和导航链接看起来像你想要的任何方式。

重定向回

最后,重定向回用户来自的位置。这很容易,因为MvcSiteMapProvider跟踪父节点。

[HttpPost]
public ActionResult Update(CustomerModel model)
{
    // Update customer here...
    var currentNode = this.GetCurrentSiteMapNode();
    if (currentNode != null)
    {
        var parentNode = currentNode.ParentNode;
        if (parentNode != null)
        {
            return Redirect(parentNode.Url);
        }
    }
    return View(model);
}

您可能希望存储来自原始父页面的一些附加信息(排序顺序、搜索项等),在这种情况下,您将需要通过Update页面传递这些参数并以某种方式返回父页面。

一种方法是使用会话,然后使用一些逻辑默认行为,如果它们在重定向页面时丢失。

另一种(更简单的)方法是将它们作为参数(查询字符串或路由值)添加到Customer Update页面的URL中,这样它们就会自动构建到返回URL中。由于您有2条不同的路由,您只需要将信息添加到适当的路由中。您只需要确保Search页面和Customer Update页面的路由都包含它们,以便正确构建父URL。

全面披露:我是MvcSiteMapProvider项目的主要贡献者。

参见:

  • https://github.com/maartenba/mvcsitemapprovider/wiki/Multiple-Navigation-Paths-to-a-Single-Page
  • http://www.shiningtreasures.com/post/2013/08/07/MvcSiteMapProvider-40-a-test-drive
  • http://www.shiningtreasures.com/post/2013/09/02/how-to-make-mvcsitemapprovider-remember-a-user-position