Prevent Form Resubmission in ASP.Net (without redirecting to myself)
Asked Answered
J

2

7

I have a master page with a Form element (<form runat="server">), a content page with a Button element (<asp:Button ID="Button1" runat="server" OnClick="Button1_Click" Text="Sync" />), and a Code Behind page that contains the Button1_Click function.

The user comes to the page and clicks the button. The Code Behind code executes (on the server) in which it updates some tables in a database. The last thing the Code Behind code does is to set the InnerHTML of a Span element on the content page with a success or failure message.

That all works great. The problem is if the user refreshes the page, the Form is resubmitted and the browser asks if that's really what the user wants. If the user responds in the affirmative then the Code Behind code is re-executed and the database is updated again. If the user responds negatively, then nothing happens.

Re-executing the Code Behind code is not a big deal. It won't hurt anything. But it's not really the behavior I want.

I know I can do a redirect back to the page with Response.Redirect() but then the user never sees my success or failure message. I should mention that the message is really more than just "Success" or "Failure" otherwise I suppose I could add something to the QueryString on the Redirect.

Is there a way to reset the Form element from the Code Behind code so that if the user refreshes the page the Form is not resubmitted?

The master page...

<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Site.master.cs" Inherits="IntuitSync.SiteMaster" %>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" xmlns:ipp="">
    <head runat="server">
    </head>
    <body>
        <form runat="server">
            <div runat="server" id="mainContetntDiv">
                <asp:ContentPlaceHolder ID="MainContent" runat="server" />
            </div>
        </form>
    </body>
</html>

The content...

<%@ Page Language="C#" AutoEventWireup="true" MasterPageFile="~/Site.master" CodeBehind="SyncToCloud.aspx.cs" Inherits="IntuitSync.SyncToCloud" %>
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
    <asp:Button ID="Button1" runat="server" OnClick="Button1_Click" Text="Sync" />
    <span runat="server" id="SyncStatus"></span>
</asp:Content>

The Code Behind...

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Globalization;
using System.Web;

namespace IntuitSync
{
    public partial class SyncToCloud : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            //
        }

        protected void Button1_Click(object sender, EventArgs e)
        {
            /*
                Do a bunch of stuff and put the results in syncResults which is a List.
            */
            SyncStatus.InnerHtml = string.Join("", syncResults.ToArray()); // I'd rather do this...
            //Response.Redirect(Request.Url.PathAndQuery, true);  // ...and not do this.
        }
    }
}

Any and all help is greatly appreciated.

Judicial answered 7/2, 2013 at 21:32 Comment(0)
P
9

The issue is you're doing a form POST which, if the user subsequently refreshes the page, does what it's supposed to do: resubmit the POST. There is no way around it. Therefore, the options available to you are as follows:

  1. Redirect to a confirmation page, passing the operation results to the confirmation page in some way (typically reload from some data repository if it's something that's not feasible for a query string/cookie/etc.).
  2. Redirect to the same page, but pass the success message via a query string parameter (or go silly and store it in the session/ViewState/what-have-you-silliness).
  3. Instead of posting, do a form get since it seems you aren't posting any values, only initiating some sort of server-side processing. To do so simply change your form from a post to a get, or just do a link. The danger here is you shouldn't be doing any transformative/destructive operations in any get operation.

NOTE: make sure you sanitize any and all user input (treat user input as evil always).

You probably would be happiest with the last option, but it all depends on what "do a bunch of stuff and put the results in syncResults..." really entails.

UPDATE (years later)

While it should be obvious it might not be obvious enough to some users: you should NEVER open yourself up to XSS attacks by directly displaying un-sanitized "success message via a query string parameter". The suggestions assumed this was obvious, but in retrospect it should have been explicitly clear in that regard.

Phyllis answered 7/2, 2013 at 22:7 Comment(8)
Thanks Ted. Yes, you are correct, I'm not actually posting any values from the Form. If it were up to me I would remove the Form element from the master page but my understanding is that the Button won't call the Code Behind code without it. I'm more of a classic ASP kind of guy and am new to ASP.NET. I just now changed it from a Post to a Get and now I get some sort of _VIEWSTATE data in the QueryString. Undobtedly ASP.Net (for lack of a better term) has to do this in order to be able to keep all of the parts together. I may just go to option 1 and stop trying to fight it.Judicial
If you don't need ViewState on the page, turn it off. In your case, you most definitely don't for this page so turn it off at the page level.Phyllis
And why bother having it need to have a button? Just provide a link to the/a URL and switch your logic from the button_click to page_load...Phyllis
I didn't know I could turn ViewState off. I'll look into that. Why the button? I wanted the user to be able to review the data before processing it. The page shows data that is in the database but needs to by synced to the user's desktop version of QuickBooks. For example, there could be 10 new customer records in the database that need to be synced with QuickBooks. The syncResults would show how many of the 10 records were successfully synced (all 10, 9 out of 10, etc...).Judicial
Personally, I would have a review page (view outstanding records), and a confirmation page (either process there which is prone to an extra record sneaking and requires non-indempotent process - marking as imported - or better yet, process on review page, save to datastore, and reload results on review page. I won't even ask how you plan on having a web app sync with your desktop QB.Phyllis
Or best yet, just do an ajax call to a service. Easy and by far the best.Phyllis
Yeah, normally I'd do something like this with ajax (js on the client and classic asp on the server). It's the QB piece that complicates it. I only know how to do that using .Net and C#. Neither of which have I used before which means I'm really not able to do anything other than use the examples I got from Intuit. But I do like the idea of turning it into a web service. Food for thought. Thanks again.Judicial
Don't quite get how you plan on implementing with the web server interacting with the desktop installation of QB...regardless of whether you are using the QB API with .NET.Phyllis
G
1

i would suggest going into JASON route

on SyncToCloud.aspx.cs

[System.Web.Services.WebMethod]
    public static string updateSyncStatus()
    {
        /*
                Do a bunch of stuff and put the results in syncResults which is a List.
            */
            SyncStatus.InnerHtml = string.Join("", syncResults.ToArray()); // I'd rather do this...
            //Response.Redirect(Request.Url.PathAndQuery, true);  // ...and not do this.
    }

on SyncToCloud.aspx

function SyncStatus(){
                $.ajax({
                            type: "POST",
                            async:true,
                            url: "syncToCloud.aspx/updateSyncStatus",
                            data: <if have anydata>,
                            contentType: "application/json; charset=utf-8",
                            dataType: "json",
                            success: function (msg) {
                                if (msg.d == "Success") {                                    
                                    //Set message on some lable or do something
                                } else {
                                    //Proceed to show message on wrong password
                                }
                            } 
                        }); 
            }

Markup on buutton

<asp:Button ID="Button1" runat="server" OnClientClick="SyncStatus(); return false;" Text="Sync" />
Globefish answered 22/7, 2015 at 3:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.