Tag Archives: Validace

Blazor – Validace formulářů [Jiří Kanda, Vzdělávací okénko, 2.6.2021]

Záznam ze Vzdělávacího okénka HAVIT, kde Jirka Kanda ukazoval, jak na validací formulářů v Blazoru:

Blazor – Formuláře [Jiří Kanda, Vzdělávací okénko, 12.2.2020]

Záznam ze Vzdělávacího okénka HAVIT z 12. února 2020, kde Jiří Kanda mluvil o formulářích, validacích a podpůrných komponentách v Blazoru.

Nahrávka je publikována na našem HAVIT YouTube Channelu.

Copy–Paste a nechtěné mezery ve formulářích

Ve formulářovém poli chceme po uživateli e-mailovou adresu nebo třeba nový login name – jaké je jeho rozčarování, když po vložení adresy pomocí CTRL-V (zkopírované např. z Excelu), aplikace hlásí
nevalidní údaj.
Na vině jsou nechtěné mezery připojené za (a někdy i před) adresu.
Nechtěné mezery nám pomůže odstranit funkce aktivovaná událostí input -> na oninput řetězec trimujeme.
Provedeme to pouze, pokud se otrimovaný a původní řetězec liší (tj. je co trimovat) – pokud bychom to dělali bez této podmínky, tak vždy, když by uživatel ručně vepsal nějaký znak, skočil by mu kurzor v políčku na konec pole (i tehdy, pokud by něco editoval uprostřed textu).

if (formField.value != formField.value.trim())
{
    formField.value = formField.value.trim();
}

Pro ASP.NET si můžeme vytvořit takovýto skin:

<asp:TextBox SkinID="TrimOnInput" oninput="if (this.value!=this.value.trim()) {this.value=this.value.trim();};" runat="server" /> 

Funkce se nehodí pro pole, která mají obsahovat mezeru mezi slovy (např. „Jan Novák“) – i když takové celé jméno lze do pole vložit pomocí CTRL-C, zcela určitě nebude možné jméno pohodlně napsat – pole se totiž chová tak, že na stisk mezerníku za slovem nereaguje.

Validátory považují white-space za empty hodnotu

Leckoho možná překvapí chování validátorů (RegularExpressionValidatoru, CompareValidatoru a dalších) v jednoduchém případě:

<asp:TextBox ID="MyTB" runat="server" />
<asp:RegularExpressionValidator ValidationExpression="\d+" ControlToValidate="MyTB" Text="x" runat="server" />
<asp:Button Text="OK" runat="server" />

Co se děje:

  • hodnota „123“ validátor aktivuje, vstup je validní
  • hodnoty „123 “ nebo “ 123″ validátor aktivují, vstup není validní
  • hodnotu “ “ (white-space) validátor propustí, vstup je podle něj validní
    • nepomáhá ani ValidationExpression=“^\d+$“, validátor se prostě neaktivuje
  • hodnota „“ (String.Empty) validátor neaktivuje, validátory nevalidují Empty vstupy
    • white-space samotný je považován za Empty a validátory se tak vyjma RequiredFieldValidatoru ignorují (a CustomValidatoru, pokud má nastaveno ValidateEmptyText=“true“)

Proč nejde udělat rozumný AjaxValidator?

Po půl dni snažení a bádání jsem dospěl k subjektivnímu závěru, že nelze udělat rozumný ASP.NET validátor založený na AJAXu. Přesněji řečeno bylo mým cílem vytvořit obdobu CustomValidatoru, který by měl běžný ServerValidate a navíc klientskou jscript část, která by pomocí AJAXového HTTP-requestu validovala hodnotu vůči tomuto ServerValidate.

Narazil jsem na následující zásadní překážky, které podle mě nelze snadno překonat:

  1. Současné ASP.NET validátory, resp. všechny jejich Microsoftí obslužné klientské skripty a API jsou nekompromisně synchronní. V okamžiku požadavku validace (obvykle před submitem stránky) jsou volány prosté validační funkce všech povolených validátorů a od těchto se očekává jen true/false dle výsledku validace. V současném konceptu validátorů nikde není rozumný prostor pro asynchronní operace či nějakého zásahu do průběhu validace. 
  2. Je prakticky nemožné rozumným způsobem provést synchronní AJAXové volání vůči serveru a stejnětak je nemožné rozumně toto synchronní volání simulovat nějakým blokováním threadu uvnitř validační funkce. Javascript nezná žádné sleep/wait/pause a veškeré snahy o jeho implementaci vždy končí v tupé smyčce zatěžující CPU ze 100% do okamžiku splnění nějaké podmínky (přijetí AJAXového callbacku, dosažení určitého času, naplnění čítače, atp.)

Pro současný koncept ASP.NET validátorů jsem dospěl k těmto závěrům:

  1. Protože je koncept založen především na jednorázové kontrole hodnot před submitem formuláře a má za účel tento submit povolit/zakázat, je oprávněně založen na synchronních operacích a pro jakékoliv dlouhotrvající operace či asynchronní volání zde není prostor a poměrně logicky se s nimi nepočítá.
  2. Asynchronní AJAXová validace vůči serveru by byla použitelná u konceptu průběžné validace, kdy by byly hodnoty kontrolovány ihned po jejich zadání (onchange) a takováto validace by díky svému zpoždění nemohla blokovat odeslání formuláře, spíše by sloužila jako proaktivní kontrola uživatelského zadání.
  3. Současný koncept validátorů připouští pouze zběsilá řešení se synchronizačním blokováním threadu v různých sleep smyčkách (byť s timeoutem), popř. „chováním nekompatibilní“ on-change implementace dle bodu 2.

Veškeré pokusy o implementace AjaxValidatoru, které jsem na netu viděl, trpěly jedním nebo několika nedostatky z výše uvedených a jejich praktická použitelnost se tak blíží nule (spíše se tak stávají přetěžovači serverů, než účinnými validátory).

Avšak! Pokud by se našel někdo, koho by napadlo, jak výše uvedená omezení účinně obejít a funkční AjaxValidator vytvořit, sem s myšlenkou a pochvala ho nemine… ;-)))

PS: Sám jsem dospěl k implementaci AjaxValidatoru, který měl nastavitelný Timeout a ve validační funkci po zavolání asynchronního AJAX requestu na ServerValidate čekal po dobu tohoto timeoutu na odezvu serveru (čekal = různé hnusné implementace zatěžujících smyček čekajících na nějaký příznak nebo timeout). Pokud do Timeoutu nebyl AJAXem výsledek od serveru získán, propustila klientská část validaci jako IsValid=true, a tak se to v případě submitu zvalidovalo na serveru, nebo v případě onchange validace dovalidovalo později asynchronně (až dorazil callback, tak se aktualizoval validátor). Výsledek mi přišel pro praxi nepoužitelný, už timeout 1s je pro ovládání webového formuláře nepříjemný, stránka nereaguje na odeslání formuláře okamžitě, proto jsem to celé zahodil… Můj poznatek je, že pokud validátorem chápeme to, co ASP.NET, tedy blokaci odeslání formuláře, tak přes AJAX cesta nevede, i kdyby to šlo bez toho cyklického pollingu s timeoutem. Leda by byla odezva v řádu milisekund, což při serverových validacích moc nehrozí (obvykle nějaký dotaz do DB na exists, atp.).

AJAX: Nefunkční validátory uvnitř UpdatePanelu

Microsoftu se podařil pozoruhodný kousek, standardní validátory ASP.NET 2.0 nefungují korektně uvnitř ajaxového UpdatePanelu – při změně obsahu panelu se původní validátory neodregistrují. Např. tak nefungují validátory v GridView editaci, ve wizzardech, atp. Obvykle vyskakuje krásná JScript chyba „null is null“.

Předchozí beta verze AJAXu to řešily upravenými verzemi validátorů, které korektně používaly ScriptManager ke své přeregistraci. V RTM verzi AJAXu však již tyto validátory nejsou a Microsoft se rozhodl, že je bude aktualizovat všem přes WindowsUpdate, nezávisle na AJAXu.

Ta horší zpráva je, že tak dosud neudělal, přestože AJAX už dávno releasoval.

…takže kdo nemá nervy čekat na opravené validátory, musí sám ručně (přes <tagMapping>) použít jejich aktualizované verze (nebo odtud), tak jak tomu bylo v betách.

Několik ValidationGroup a jedno post-back tlačítko

Občas se nám může objevit potřeba, kdy potřebujeme formulář rozdělit na několik zón (ValidationGroup) a validaci v nich řídit samostatně, například podle toho, jako zónu si uživatel vybral radio-buttony.

ASP.NET bohužel ve verzi 1.1 nějaké podrobnější řízení validace nepodporuje, ve verzi 2.0 už dává alespoň validation groups (kterých využijeme), nicméně stále jejich použití váže na samostatnou inicializaci post-backu (pro každou validation group se předpokládá samostatné odesílací tlačítko).

Pokud potřebujeme formulář s jediným tlačítkem, který bude validovat na základě nějakého přepínače, který obsahuje (typicky na základě radio-buttonů nebo i drop-down-listu), pak nastávají potíže.

Validace na straně serveru

Pokud nám postačuje validace na straně serveru, je situace poměrně jednoduchá, protože využijeme metody Page.Validate(vaidationGroup) a následně ověřujeme už jenom Page.IsValid.

Validátory si rozdělíme na několik skupin

  1. ty, které chceme vyhodnocovat vždy, těm vlastnost ValidationGroup nenastavíme,
  2. ty, které chceme vyhodnocovat na základě přepínače, ty umístíme do příslušné skupiny ValidationGroup

Post-backovému tlačítku pak ValidationGroup nenastavíme, čímž dosáhneme toho, že i na klientu se nám budou vyhodnocovat alespoň ty společné validátory, které nemají nastaveno ValidationGroup.

V obsluze události na straně serveru pak postupujeme zhruba takto:

private void Tlacitko_Click(object sender, EventArgs e)
{
   // validátory bez ValidationGroup jsou vyhodnoceny
   // stačí se tedy ptát Page.IsValid
   if (IsValid)
   {
      if (PrepinacJednaRB.Checked)
      {
         // radio-buttonový přepínač je nastaven na zónu 1
         // vyvoláme si tedy vyhodnocení validátorů zóny 1
         Validate("ValidationGroupJedna");
         if (IsValid)
         {
            ...
            // obsluha zóny 1
         }
         else
         {
            return;
         }
      }
      else if (PrepinacDvaRB.Checked)
      {
         // přepínač je nastaven na zónu 2
         Validate("ValidationGroupDva");
         if (IsValid)
         {
            ...
            // obsluha zóny 2
         }
         else
         {
            return;
         }
      }
  
      // tady můžeme obsloužit controly společné pro všechny zóny
      // je splněna obecná validace i prošla validace zvolené zóny
      // takže třeba
      result.Save();
   }
}

Ještě je třeba doplnit, že i control ValidationSummary zobrazuje pouze hlášky příslúšné ValidationGroup, tedy bychom měly mít tolik ValidationSummary, kolik máme validation groups.

Validace na straně klienta

Požadujeme-li stejné chování validátorů už na straně klienta, je situace složitější. Musíme si totiž vytvořit klientský skript, který nejprve rozhodne na základě hodnot formuláře, která zóna je vybrána (který radio-button je zakliknut) a zavolá validace pro příslušné ValidationGroup.

Volání post-backu s příslušnou validací si vygenerujeme pomocí ClientScriptManager.GetPostBackEventReference(), kam přes PostBackOptions předámeValidationGroup. Horší to bude s vícenásobnou validací (např. společná + group), kde už si budeme muset pomoct nekonvenčními metodami a volat JScript ASP.NETu natvrdo.

Vzhledem k tomu, že v praxi pro takovéto klientské validace existují hotové a promyšlené validační controly třetích stran, nemá smysl se do této oblasti hlouběji potápět…

Připojuji také odkazy na několik článků, které popisují řízení validace na straně klienta: