为啥不像 'IsPostBack' 那样实现 'IsRefresh'、'IsBackPost' 呢?

Posted

技术标签:

【中文标题】为啥不像 \'IsPostBack\' 那样实现 \'IsRefresh\'、\'IsBackPost\' 呢?【英文标题】:Why not implement 'IsRefresh', 'IsBackPost' just like is 'IsPostBack'?为什么不像 'IsPostBack' 那样实现 'IsRefresh'、'IsBackPost' 呢? 【发布时间】:2011-01-17 07:14:06 【问题描述】:

我可以看到,我自己和很多人都在 ASP.NET 中遇到这两个项目的问题......刷新按钮,后退按钮......(我看到很多人问如何禁用这两个按钮在浏览器中..)

现在在Page中实现另外两个布尔变量(IsRefresh,IsPostBack)有什么问题...如果可以规避和实现这些问题,这对开发人员来说将是一笔巨大的财富...

当您回答时,如果您还可以在您的网络应用程序中包含您正在采取的步骤,以避免在这种情况下重新发布(有时在数据库中)会有所帮助。

谢谢

【问题讨论】:

相关:***.com/questions/1791416/… 【参考方案1】:

这不是很清楚。服务器识别“回发”的唯一方法是根据您的请求传递给页面的标头。当您发布表单时,“IsPostBack”为真,如您所料。当您刷新页面或按返回时,会发送完全相同的请求数据。如果不更改发送请求的浏览器的行为,应用程序无法检测到用户发出了非标准请求(返回或刷新)。

如您所知,大多数浏览器都会向您指示“单击返回将重新发送表单数据...”,但这只是浏览器发出的警告,让您知道它将发送完全相同的数据再次请求。服务器不知道这一点,也没有(本机)方法来解释信息。

双重回发预防技巧

防止数据被两次发布的一种方法是确保每个 PostBack 都包含一些您可以验证的唯一数据。这个过程相当简单。

在每次页面加载时,您都需要为该页面的 PostBack 事件创建一个唯一标识符。唯一 id 是什么并不重要,只要它在顺序页面加载时不一样。将该唯一标识符放在页面上的隐藏字段和用户会话(或 cookie)中。然后,在每个 PostBack 验证隐藏字段中的 cookie 匹配会话中的值。如果值匹配,则 PostBack 是可以相应处理的页面的原始帖子。执行必要的操作后,您将需要再次更改两个位置的唯一标识符。这样,如果用户回击并选择“重新发送数据”,隐藏字段将与会话密钥不匹配,您可以丢弃重复的帖子数据。

【讨论】:

我想(读作绝望)知道,在这种情况下我应该采取什么步骤,以避免在数据库中执行另一个调用/命令......一个经典的例子是用户输入 cmets 的页面...点击刷新再次插入评论,并且很难检查是否已经输入了相同的评论,因为评论很长,并且有可能有效地输入了相同的评论。 @The King:很简单。将评论添加到数据库后,您向同一页面发出 Response.Redirect。然后当用户刷新或返回时不会再次添加评论。【参考方案2】:

实现这两个额外的布尔属性的问题是,确实没有(可靠的)方法来区分 Back / Refresh 触发的请求。当点击这些按钮时,浏览器会:

    如果允许,则显示缓存中的页面,或者 再次执行完全相同的请求(可能要求用户先确认)

您正在遇到案例 #2。当第二个请求发生时,服务器将收到与原始请求相同的完全相同的请求数据。因此,ASP.NET(和大多数其他框架)将像处理原始请求一样处理请求。有几种方法可以解决这个问题:

    更改缓存策略以允许浏览器重新显示先前请求的结果(可能会解决 Back 问题,但不能解决 Refresh 问题。 添加一个隐藏字段(或ViewState 中的数据),该字段包含预期回发的页面上的唯一值。然后添加一些数据结构以保留“已使用”值的列表,以及在执行任何关键操作之前测试重复项的逻辑。 使用Post/Redirect/Get pattern 确保POST 请求永远不会出现在浏览器历史记录中。

解决方案 #3 非常简单,几乎适用于所有情况。基本上,与其返回结果/确认作为对“关键”回发的回复,不如重定向到新的 URL:

protected void btnCritical_Click(object sender, EventArgs e)

    DoSomethingThatShouldNotBeDoneTwice();
    Response.Redirect("confirmation.aspx");

【讨论】:

【参考方案3】:

aspnet_isapi 识别来自表单内容的回发,特别是 ViewState 字段。它没有内在的方法来区分回发和刷新。

过去我已经看到了一些解决此问题的策略。一种是为每个请求分配一个 guid,并将其用作您正在写入的表中的唯一键或索引。

您可以创建一个所有页面都继承自的基本页面。

public partial class PageBase : System.Web.UI.Page

    private Guid _requestId;

    protected Guid RequestId
    
        get
        
            return _requestId;
           
    
    protected virtual void Page_Load(object sender, EventArgs e)
    
        if (!IsPostBack)
        
            _requestId = Guid.NewGuid();
            ViewState.Add("requestId", _requestId);
        
        else
        
            _requestId = (Guid)ViewState["requestId"];
        
    


public partial class _Default : PageBase

    protected override void Page_Load(object sender, EventArgs e)
    
        base.Page_Load(sender, e);

        // now do stuff
    

【讨论】:

是的,您可以使用它来实现 jorns 建议跟踪“已使用”请求。

以上是关于为啥不像 'IsPostBack' 那样实现 'IsRefresh'、'IsBackPost' 呢?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 glm::vec3 不像浮动数组那样排序[关闭]

为啥指向成员函数的指针不像数据指针那样只是内存地址

为啥 flexbox 不像 div 中的其他元素那样居中我的图像?

为啥 Swift 不像 Java 或 C# 那样对属性使用 getter 和 setter?

为啥代客共享不像本地代客那样在请求中添加斜线

在 scala 中创建的图像看起来不像预期的那样。不知道为啥