← Område 5 Lektion 33 · Validering Kringgå klientvalideringen

Webbserverprogrammering 1 · område 5 · lektion 33

En rad som är hela skillnaden

Två formulär som är identiska utom en rad. Det ena tar emot ett tomt namn, 500 biljetter och en e-postadress som inte är en. Det andra stoppar alla tre. Sedan tar du bort spärren i webbläsaren och ser om servern klarar sig utan den.

Missade du passet?

Det här är lektion 33:s demo, och den går att göra själv på tjugo minuter. I ordning:

  1. Lägg in de tre filerna nedan i ditt projekt – modellen i Models/Demo/, formulären i Components/Pages/. 5 min.
  2. Kör, och skicka samma dåliga värden till båda adresserna. 3 min.
  3. Gör DevTools-testet på formuläret med validering. 5 min.
  4. Gör sedan samma test på ditt eget formulär från guidens steg 11. Det är ett C-krav i inlämningen. 5 min.

Demot behöver ingen databas. Har du inte fått migrationen att gå igenom kan du ändå göra det här.

1. Modellen

En anmälan med tre fält och tre regler. Klassen ligger inte i databasen – poängen är enbart valideringen.

using System.ComponentModel.DataAnnotations;

namespace Filmarkivet.Models.Demo;

// Demomodell till lektion 33. Ligger inte i databasen – poängen är enbart
// valideringen.
public class Anmalan
{
    [Required(ErrorMessage = "Namnet måste fyllas i.")]
    [StringLength(40, ErrorMessage = "Namnet får vara högst 40 tecken.")]
    public string Namn { get; set; } = "";

    [Range(1, 10, ErrorMessage = "Antalet biljetter är 1 till 10.")]
    public int Biljetter { get; set; } = 1;

    [EmailAddress(ErrorMessage = "Det där ser inte ut som en e-postadress.")]
    public string Epost { get; set; } = "";
}

Tre attribut, tre svenska felmeddelanden. Lägg märke till att de bara står där. Ingenting i klassen kör dem – de är regler som ligger och väntar på att någon ska läsa dem. Det var det du såg på lektion 31: du satte [Required], byggde, och ingenting hände.

2. Två formulär

Först det som saknar valideringen. Det tar emot vad som helst:

@page "/demo/utan-validering"
@using Filmarkivet.Models.Demo

@* Samma formulär som MedValidering.razor – utom en enda rad. *@

<h1>Utan validering</h1>

<EditForm Model="Formular" OnValidSubmit="TaEmot" FormName="utan">

    <p><label>Namn<br /><InputText @bind-Value="Formular!.Namn" /></label></p>
    <p><label>Antal biljetter<br /><InputNumber @bind-Value="Formular!.Biljetter" /></label></p>
    <p><label>E-post<br /><InputText @bind-Value="Formular!.Epost" /></label></p>

    <button type="submit">Skicka</button>
</EditForm>

@if (mottagen is not null)
{
    <p class="tomt">
        Mottaget: <b>@mottagen.Namn</b> · @mottagen.Biljetter biljetter ·
        @mottagen.Epost
    </p>
}

@code {
    [SupplyParameterFromForm]
    private Anmalan? Formular { get; set; }

    private Anmalan? mottagen;

    protected override void OnInitialized()
    {
        Formular ??= new Anmalan();
    }

    private void TaEmot()
    {
        mottagen = Formular;
    }
}

Och det som har den:

@page "/demo/med-validering"
@using Filmarkivet.Models.Demo

@* Identisk med UtanValidering.razor, plus DataAnnotationsValidator och
   meddelandena. Det är hela skillnaden. *@

<h1>Med validering</h1>

<EditForm Model="Formular" OnValidSubmit="TaEmot" FormName="med">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <p>
        <label>Namn<br /><InputText @bind-Value="Formular!.Namn" /></label>
        <ValidationMessage For="() => Formular!.Namn" />
    </p>
    <p>
        <label>Antal biljetter<br /><InputNumber @bind-Value="Formular!.Biljetter" /></label>
        <ValidationMessage For="() => Formular!.Biljetter" />
    </p>
    <p>
        <label>E-post<br /><InputText @bind-Value="Formular!.Epost" /></label>
        <ValidationMessage For="() => Formular!.Epost" />
    </p>

    <button type="submit">Skicka</button>
</EditForm>

@if (mottagen is not null)
{
    <p class="tomt">
        Mottaget: <b>@mottagen.Namn</b> · @mottagen.Biljetter biljetter ·
        @mottagen.Epost
    </p>
}

@code {
    [SupplyParameterFromForm]
    private Anmalan? Formular { get; set; }

    private Anmalan? mottagen;

    protected override void OnInitialized()
    {
        Formular ??= new Anmalan();
    }

    private void TaEmot()
    {
        mottagen = Formular;
    }
}

Kör, och fyll i samma värden i båda:

FältSkriv
Namnlämna tomt
Antal biljetter500
E-postinte-en-adress
AdressVad som händer
/demo/utan-valideringMottaget: · 500 biljetter · inte-en-adress. Allt gick igenom
/demo/med-valideringTre felmeddelanden. Ingenting togs emot

Webbläsaren kan protestera på 500 innan du hinner skicka – InputNumber renderar type="number". Skicka ändå, eller läs vidare: det är precis den spärren du snart tar bort.

3. Raden som skiljer

Läs de två filerna bredvid varandra och leta upp skillnaden innan du läser vidare.

Svaret: <DataAnnotationsValidator />. Den syns inte på skärmen och tar inte emot något värde, och det är därför den är så lätt att glömma.

Det andra formuläret har också <ValidationSummary /> och tre <ValidationMessage> – men de visar bara felen. Utan validatorn finns det inga fel att visa, för ingen har läst attributen.

Attributen på klassen är regler som ligger och väntar

De gör ingenting av sig själva. DataAnnotationsValidator är den som läser dem. Tar du bort den raden har du en modell full av regler och ett formulär som inte känner till en enda av dem.

Och OnValidSubmit hör ihop med den: metoden körs bara när validatorn sagt att allt är i ordning. Med OnSubmit i stället körs metoden oavsett – och då har du byggt ett formulär som sparar skräp, med en validator som bara visar röd text.

4. Kringgå klientvalideringen

Skriv 500 i biljettfältet på /demo/med-validering. Webbläsaren stoppar dig, eller så gör ValidationSummary det. Det bevisar ingenting. Allt som händer i webbläsaren är besökarens att stänga av.

Gör så här i stället:

  1. Öppna /demo/med-validering och tryck F12.
  2. Gå till fliken Elements. Tryck Ctrl+F och sök på Biljetter för att hitta fältet.
  3. Dubbelklicka på type="number" och ändra till type="text". Finns min eller max, ta bort dem.
  4. Skriv 500 i fältet och skicka.

Servern svarar med sidan tillbaka, och i listan står:

Antalet biljetter är 1 till 10.

Ingenting togs emot. DataAnnotationsValidator körde attributen på servern, efter att formuläret kommit in – och den bryr sig inte om vad webbläsaren tyckte.

Klientvalidering är för användarens skull. Serversidesvalidering är för systemets.

Det som händer i webbläsaren gör att användaren slipper vänta på ett svar för att få veta att fältet är tomt. Det är bekvämlighet, inte ett skydd. Den som bara har den första har ingen validering alls.

Gör sedan samma test på ditt eget formulär, på ett fält med [Range] eller [StringLength]. Skriv i README.md vilket fält du testade, vad du ändrade och vad servern svarade – det är ett C-krav.

5. Vad som fångas, och vad som inte gör det

Prova a@b i e-postfältet. Det går igenom. [EmailAddress] kontrollerar formen – att det finns ett @ med något på båda sidor – inte att adressen finns.

Det är värt att säga rakt ut vad valideringsattribut är till för:

FångarFångar inte
Ett tomt fältEn påhittad adress som ser rätt ut
500 där det ska stå 1–10Ett värde som är tillåtet men fel
En text som är för långNågon som medvetet försöker
Slarv och misstagAngrepp

Och kedjan har, som du såg på lektion 32, exakt ett ställe som håller. Databasen fångar inte betyget 99 – [Range] blev ingenting i tabellen. Webbläsaren fångar det bara tills någon trycker F12. Det som återstår är DataAnnotationsValidator plus OnValidSubmit, på servern.

Den som medvetet försöker är område 7.