Zamezení vícenásobného odeslání formuláře pomocí server control v ASP.NET

10. září 2004

O tom, že v řadě webových aplikací je nežádoucí, aby uživatel odesílal údaje z formuláře vícekrát, a jak tomu alespoň klientským skriptem zamezit, jsme již psali. V tomto článku popsaný způsob zapouzdříme do serverového ovládacího prvku, který je pak možné ihned použít jako náhradu původního HtmlFormu.

Je zřejmé, že vícenásobné odeslání těch samých údajů uživatelem je nežádoucí. Lékem proti „syndromu F5“, kdy uživatel opakovaně odesílá formulář obnovováním stránky, je prostá úprava aplikace tak, aby po zpracování údajů provedla přesměrování pomocí Response.Redirect(). Tento princip však řeší situaci až po odeslání. Jak jsme již psali, kritickým momentem je však také stav těsně po kliknutí na odesílací tlačítko – pokud není ze serveru dostatečně rychlá odezva, uživatel klidně na odesílací tlačítko klikne v tento okamžik opakovaně i několikrát a problém je na světě, do naší aplikace nakonec dorazí několik požadavků s těmi samými údaji. Ukázka (zdrojový kód) je také tentokrát spíše symbolická, pokud máte rychlé připojení, pravděpodobně se vám nepodaří nasimulovat tak rychlé opakované kliknutí, aby aplikace zareagovala upozorňujícím alertem. Pokud budete ale ukázku testovat na vlastním počítači, mám pro vás jeden trik. Po prvním zobrazení stránky přeuložte soubor Web.config, pokud následně kliknete na odesílací tlačítko, bude nejprve probíhat kompilace a ta vytvoří dostatek času pro ozkoušení vícenásobné odeslání.

V přechozím článku popsaný skript zapíšeme do kódu třídy, kterou zdědíme ze základní třídy HtmlForm v prostoru názvů System.Web.UI.HtmlControls. Jak vyrobit serverový ovládací prvek jsme si již vyzkoušeli na příkladu s Flash Playerem, postup je analogický. Připravený klientský skript zakomponujeme do přepsané metody Render() a pro pohodlné používání zveřejníme potřebné vlastnosti. V našem případě budou dvě, AllowMultipleSubmit typu „Boolean“ prostě umožní vypnout náš doplněk zamezující možnost vícenásobného odeslání (může se hodit například při ladění aplikace), MultipleSubmitMessage je typu „String“ a umožní nastavit případné hlášení uživateli, pokud se pokouší formulář odeslat opakovaně.

using System;
using System.Web.UI;
namespace Interval.CZ.Web.UI.HtmlControls
{
  [System.Web.UI.ToolboxData(„<{0}:HtmlForm RunAt=\“Server\“></{0}:HtmlForm>“)]
  public class HtmlForm : System.Web.UI.HtmlControls.HtmlForm
  {
    private String _multipleSubmitMessage = String.Empty;
    private Boolean _allowMultipleSubmit = false;
    public Boolean AllowMultilpleSubmit
    {
      get
      {
        object o = this.ViewState[„AllowMultilpleSubmit“];
        if (o != null)
          return (Boolean) o;
        else
          return false;
      }
      set
      {
        this.ViewState[„AllowMultilpleSubmit“] = value;
        _allowMultipleSubmit = value;
      }
    }
    public String MultipleSubmitMessage
    {
      get
      {
        object o = this.ViewState[„MultipleSubmitMessage“];
        if (o != null)
          return (String) o;
        else
          return String.Empty;
      }
      set
      {
        this.ViewState[„MultipleSubmitMessage“] = value;
        _multipleSubmitMessage = value;
      }
    }
    protected override void Render (HtmlTextWriter writer)
    {
      if (!_allowMultipleSubmit)
      {
        base.Attributes.Add(„onsubmit“,“__formDisMultipleClick(document.getElementById(‚“ + base.ClientID + „‚));“);
        writer.WriteLine();
        writer.WriteBeginTag(„script“);
        writer.WriteAttribute(„type“,“text/javascript“);
        writer.WriteLine(HtmlTextWriter.TagRightChar);
        writer.WriteLine(„<!– <![CDATA[„);
        writer.WriteLine(„function __formIsSending()“);
        writer.WriteLine(„{„);
        if (_multipleSubmitMessage != null && _multipleSubmitMessage.Length > 0)
          writer.WriteLine(“ alert(‚“ + _multipleSubmitMessage + „‚);“);
        writer.WriteLine(“ return false;“);
        writer.WriteLine(„}“);
        writer.WriteLine(„function __formDisMultipleClick(formObj)“);
        writer.WriteLine(„{„);
        writer.WriteLine(“ for (var i=0; i<formObj.length; i++ )“);
        writer.WriteLine(“ {„);
        writer.WriteLine(“ if (formObj.elements[i].type == ‚submit‘ && formObj.elements[i].type == ‚image‘)“);
        writer.WriteLine(“ {„);
        writer.WriteLine(“ formObj.elements[i].onclick = __formIsSending;“);
        writer.WriteLine(“ }“);
        writer.WriteLine(“ }“);
        writer.WriteLine(„}“);
        writer.WriteLine(„//]]> –>“);
        writer.WriteEndTag(„script“);
        writer.WriteLine();
      }
      base.Render(writer);
    }
  } }

V kódu vidíme „poctivé“ vytvoření vnitřních private proměnných, abychom vyhověli zásadám OOP (zjednodušeně řečeno, uvnitř prvku pracujeme s vnitřními proměnnými, které „vnější“ aplikace nevidí – na tyto vnitřní proměnné pouze mapujeme veřejné neboli public vlastnosti, se kterými se pak zvnějšku pracuje). V přepsané (override) metodě Render() si nejprve necháme vykreslit část klientského skriptu (v aplikaci se potom skript objeví ještě před otevírací značkou form) a následně zavoláme výkonnou část původní metody Render() zápisem klíčového slova base. Třídu nakonec zkompilujeme a uložíme do složky Bin v kořeni virtuální aplikace na serveru.

Zde bych chtěl jen upozornit na drobný rozdíl oproti původnímu JavaScriptu v předchozím článku. Jak vidíte, do atributu onsubmit nepředáváme funkci __formDisMultipleClick() odkaz na formulář klíčovým slovem this. Důvodem je nový způsob renderování klientských skriptů v ASP.NET 2.0. Když se totiž pokusíme cokoli přidat do atributu onsubmit formuláře, ASP.NET to rozpozná a místo renderování přímo do atributu onsubmit zakomponuje naše příkazy do těla vlastní funkce obsluhující událost odeslání formuláře. Zde by nám pochopitelně odkaz this nezafungoval, proto je „poctivě“ vypsáno získání přístupu k objektu pomocí document.getElementById('" + base.ClientID + "'), kde prostřednictvím ClientID získáme id, se kterým je formulář renderován do stránky.

Ukázka použití nového prvku ve stránce:

<%@ Page Language=“C#“ EnableSessionState=“False“ Debug=“False“ %>
<%@ Register TagPrefix=“Sc“ Namespace=“Interval.CZ.Web.UI.HtmlControls“ Assembly=“Interval.CZ.Web.UI.HtmlControls.HtmlForm“ %><?xml version=“1.0″ encoding=“utf-8″
?>
<!DOCTYPE html PUBLIC „-//W3C//DTD XHTML 1.0 Transitional//EN“ „http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd“>
<html xmlns=“http://www.w3.org/1999/xhtml“ xml:lang=“cs“ lang=“cs“ dir=“ltr“>
  <head>
    <meta http-equiv=“Content-type“ content=“text/html; charset=utf-8″ />
    <title>Vlastní HtmlForm povolí odeslání pouze 1x</title>
  </head>
  <body>
    <Sc:HtmlForm MultipleSubmitMessage=“Již se odesílá…“ runat=“server“>
      <asp:TextBox Id=“txt1″ RunAt=“Server“ />
      <asp:RequiredFieldValidator ControlToValidate=“txt1″ Display=“Dynamic“ ErrorMessage=“Chyba“ RunAt=“Server“ />
      <asp:Button RunAt=“server“ Text=“Odeslat 1″ />
      <asp:Button RunAt=“server“ Text=“Odeslat 2″ />
      <asp:Button RunAt=“server“ Text=“Odeslat 3″ />
    </Sc:HtmlForm>
  </body>
</html>

V direktivách stránky vidíme zaregistrování ovládacího prvku, kde jsme nadefinovali předponu elementu „Sc“, která je potom skutečně použita i v elementu našeho nového prvku. V elementu našeho prvku jsme pak nastavili ještě text hlášení (atributem MultipleSubmitMessage), které se zobrazí, pokud uživatel „kliká více než je zdrávo“. Pro ukázku je přidán i schvalovací prvek RequiredFieldValidator, aby bylo zřejmé, že klientský skript přidaný do nového HtmlFormu nekoliduje se skripty, které schvalovací prvky navazují na obsluhu události onsubmit formuláře.

Jak vidíte, libovolnou aplikaci můžeme jednoduše upravit tak, že původní element formuláře nahradíme za tento náš nový, který podporuje všechny vlastnosti původního a navíc se brání vícenásobnému odeslání.

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

Štítky: Články

Mohlo by vás také zajímat

Nejnovější

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *