← Område 4 Lektion 29 · Sex fel som flyttar sig Poängen

Webbserverprogrammering 1 · område 4 · lektion 29

Sex fel som flyttar sig

Sex fel, tolv filer. Det som är nytt i det här området är inte felen i sig – det är när de kommer. Två stoppar bygget, ett stoppar servern innan den hunnit lyssna, två stoppar bara den sida du öppnade, och ett stoppar ingenting alls. Det sista är det farligaste.

Svara innan du läser vidare

Orsakerna i tabellen nedan ligger dolda tills du klickar på raden. De är inte värda mycket förrän du har ett eget svar att jämföra med – ge dig själv tre minuter först.

Vill du köra koden: lägg .cs-filerna i Services/Fel/ och .razor-filerna i Components/Pages/. Läs rutan vid varje fel om vad som ska registreras. Fel 3, 4 och 5 kan inte ligga i projektet samtidigt som de andra – de stoppar bygget eller servern, vilket är hela deras poäng.

1. De sex meddelandena

Klicka på en rad för att se orsaken. Meddelandena är avskrivna ur terminalen, inte ur minnet.

FelmeddelandetOrsaken
There is no registered service of type '…IReservation'. Gränssnittet finns, klassen finns, men Program.cs har aldrig fått veta hur de hänger ihop. Det tredje steget glömdes.
There is no registered service of type '…Utlaning'. Samma meddelande, men lägg märke till att I saknas i namnet. Sidan ber om klassen, och registreringen gäller gränssnittet.
CS0542: 'Fel3': member names cannot be the same as their enclosing type Injektionsnamnet är samma som filnamnet. Fel3.razor blir klassen Fel3, och en klass får inte ha en medlem som heter som den själv.
CS0535: 'Fel4Utlaning' does not implement interface member 'IUtlaning.AntalUtlanade()' Klassen har lovat att uppfylla gränssnittet men saknar en av metoderna. Ofta ett stavfel, eller en signatur som skiljer sig på returtypen.
Cannot consume scoped service '…IUtlaning' from singleton '…Fel5Tjanst'. En singleton lever tills servern startas om. Den scoped tjänst den fick vid starten hade försvunnit direkt efter första förfrågan – och singletonen hade hållit kvar en död referens för alltid.
Inget meddelande alls. Samlingen ligger i en singleton. Alla besökare delar samma lista, och det syns bara om två personer använder sajten samtidigt.

2. Fel 1 – tjänsten som aldrig registrerades

Gränssnittet är skrivet:

namespace Filmarkivet.Services.Fel;

// Gränssnittet finns, klassen finns – men ingen har registrerat dem i
// Program.cs. Det är hela felet i Fel1.razor.
public interface IReservation
{
    int AntalReserverade();
}

Klassen är skriven:

namespace Filmarkivet.Services.Fel;

public class Reservation : IReservation
{
    public int AntalReserverade()
    {
        return 2;
    }
}

Och sidan på /fel-di/1 ber om den:

@page "/fel-di/1"
@inject Filmarkivet.Services.Fel.IReservation Reservationer

<h1>Fel 1</h1>

<p>Antal reserverade: @Reservationer.AntalReserverade()</p>

Bygget ger noll fel och noll varningar. Servern startar. Sidan svarar 500, och terminalen säger:

System.InvalidOperationException: Cannot provide a value for property
'Reservationer' on type 'Filmarkivet.Components.Pages.Fel1'. There is no
registered service of type 'Filmarkivet.Services.Fel.IReservation'.

Rättningen: en rad i Program.cs, före builder.Build():

builder.Services.AddScoped<IReservation, Reservation>();

Meddelandet är ovanligt hjälpsamt. Det säger vilken egenskap på vilken sida som frågade, och vilket gränssnitt som saknar svar. Tre uppgifter, och alla tre behövs för att hitta raden som fattas.

3. Fel 2 – injektion mot klassen

Här finns registreringen. Den ser ut så här:

builder.Services.AddScoped<IUtlaning, Utlaning>();

Gränssnittet och klassen:

namespace Filmarkivet.Services.Fel;

// Gränssnittet de sex felen kretsar kring. Håller sig med en enda metod, så
// att felmeddelandena inte drunknar i kod som inte hör till saken.
public interface IUtlaning
{
    int AntalUtlanade();
}

Och sidan på /fel-di/2:

@page "/fel-di/2"
@inject Filmarkivet.Services.Fel.Utlaning Tjanst

<h1>Fel 2</h1>

<p>Antal utlånade: @Tjanst.AntalUtlanade()</p>

Terminalen säger nästan samma sak som förra gången:

System.InvalidOperationException: Cannot provide a value for property
'Tjanst' on type 'Filmarkivet.Components.Pages.Fel2'. There is no registered
service of type 'Filmarkivet.Services.Fel.Utlaning'.

Rättningen: @inject …IUtlaning Tjanst. Ett tecken.

Läs sista ordet i meddelandet

Skillnaden mellan fel 1 och fel 2 är ett enda I långt inne i en lång rad, och det är där hela informationen sitter. Saknas I är det inte registreringen som fattas – det är sidan som frågar efter fel sak.

Registreringen säger: ber någon om IUtlaning, ge dem en Utlaning. Den säger ingenting om vad som händer om någon ber om Utlaning direkt. Och det ska den inte heller – hela poängen är att komponenterna inte ska känna till klassen.

4. Fel 3 – namnet som krockar

Den här filen bygger inte. Lägg in den ensam, läs meddelandet, ta bort den igen:

@page "/fel-di/3"
@inject Filmarkivet.Services.Fel.IUtlaning Fel3

<h1>Fel 3</h1>

<p>Antal utlånade: @Fel3.AntalUtlanade()</p>

dotnet build säger:

Fel3.razor(2,44): error CS0542: 'Fel3': member names cannot be the
same as their enclosing type

Rättningen: döp om injektionen. Utlaningen, Registret, vad som helst utom Fel3.

Felet låter konstigt tills man vet att Fel3.razor blir en klass som heter Fel3, och att @inject … Fel3 gör en egenskap som också heter Fel3. En klass får inte ha en medlem med sitt eget namn. Det blir aktuellt i det här området eftersom du plötsligt namnger saker inuti sidor – i område 3 gjorde du inte det.

Det här händer på riktigt

Den vanligaste varianten är en sida som heter Filmer.razor med @inject IFilmService Filmer. Namnet ligger nära till hands just för att sidan handlar om filmer.

Vanan som löser det: namnge injektionen efter vad tjänsten är, inte efter vad sidan visar. Arkivet, inte Filmer.

5. Fel 4 – löftet som inte hålls

Klassen säger sig uppfylla IUtlaning:

namespace Filmarkivet.Services.Fel;

// Säger sig uppfylla gränssnittet, men metoden heter något annat.
public class Fel4Utlaning : IUtlaning
{
    public int Antal()
    {
        return 3;
    }
}

dotnet build säger:

Fel4.cs(4,29): error CS0535: 'Fel4Utlaning' does not implement
interface member 'IUtlaning.AntalUtlanade()'

Rättningen: döp om Antal till AntalUtlanade, så att signaturen stämmer exakt.

Meddelandet säger vilken metod som saknas, med full signatur. Det räcker nästan alltid för att se vad som är fel – och de gånger det inte gör det är returtypen inblandad: int och Task<int> är två olika metoder, hur lika namnen än ser ut.

6. Fel 5 – scoped inuti en singleton

Tjänsten i sig är oklanderlig:

namespace Filmarkivet.Services.Fel;

// Registreras som singleton och ber om en scoped tjänst i konstruktorn:
// builder.Services.AddSingleton<Fel5Tjanst>();
public class Fel5Tjanst
{
    private readonly IUtlaning _utlaning;

    public Fel5Tjanst(IUtlaning utlaning)
    {
        _utlaning = utlaning;
    }

    public int Antal()
    {
        return _utlaning.AntalUtlanade();
    }
}

Felet ligger inte i filen utan i kombinationen av två rader i Program.cs:

builder.Services.AddScoped<IUtlaning, Utlaning>();
builder.Services.AddSingleton<Fel5Tjanst>();

Bygget ger noll fel. dotnet run avbryts innan servern hunnit lyssna på en enda förfrågan:

Unhandled exception. System.AggregateException: Some services are not
able to be constructed (Error while validating the service descriptor
'ServiceType: Filmarkivet.Services.Fel.Fel5Tjanst Lifetime: Singleton
ImplementationType: Filmarkivet.Services.Fel.Fel5Tjanst': Cannot consume
scoped service 'Filmarkivet.Services.Fel.IUtlaning' from singleton
'Filmarkivet.Services.Fel.Fel5Tjanst'.)

Rättningen: gör Fel5Tjanst scoped, eller be om IUtlaning när den behövs i stället för i konstruktorn.

Meddelandet du behöver står längst ned

Utskriften är lång och överst står AggregateException, som inte säger något. Raden som betyder något är den innersta: Cannot consume scoped service X from singleton Y. Leta efter den, inte efter det första du ser.

Och lägg märke till varför det över huvud taget är ett fel: en singleton lever tills servern startas om. Den scoped tjänst den fick vid starten hade upphört att gälla efter första förfrågan – och singletonen hade hållit kvar den för alltid. Ramverket stoppar dig innan det hinner bli en bugg.

7. Fel 6 – felet utan felmeddelande

Registret:

namespace Filmarkivet.Services.Fel;

// Registreras som singleton:
// builder.Services.AddSingleton<DelatRegister>();
//
// Ett enda objekt för hela servern, och därför en enda lista för alla
// besökare samtidigt. Koden är riktig. Livstiden är fel.
public class DelatRegister
{
    private readonly List<string> _titlar = new();

    public IReadOnlyList<string> Alla => _titlar;

    public void LaggTill()
    {
        _titlar.Add($"Film nr {_titlar.Count + 1}");
    }
}

Och sidan:

@page "/fel-di/6"
@inject Filmarkivet.Services.Fel.DelatRegister Registret

<h1>Fel 6</h1>

<p>Din lista:</p>

<ul>
    @foreach (var titel in Registret.Alla)
    {
        <li>@titel</li>
    }
</ul>

<form method="post" @formname="lagg-till" @onsubmit="LaggTill">
    <AntiforgeryToken />
    <button type="submit">Lägg till en film</button>
</form>

@code {
    private void LaggTill()
    {
        Registret.LaggTill();
    }
}

Bygget går igenom. Servern startar. Sidan fungerar. Terminalen är tyst.

Gör så här:

  1. Öppna /fel-di/6 och tryck på knappen två gånger. Två rader.
  2. Öppna samma adress i ett privat fönster.

Båda raderna står där. Du ser någon annans lista, och de ser din.

Rättningen: AddScoped i stället för AddSingleton – och eftersom ett scope är en enda förfrågan behöver listan dessutom lagras i session för att överleva till nästa sidvisning. Det är precis vad Visningslista gör i guidens steg 13.

Det här är det enda av de sex du kan lämna in av misstag

De fem andra tvingar dig att göra något. Det här gör ingenting förrän någon annan än du använder sajten – och det gör aldrig någon medan du utvecklar ensam på din egen dator.

Kontrollen är tio sekunder i ett privat fönster, och den står som ett eget krav i inlämningen just därför.

8. Poängen

Ordningen är inte slumpmässig. Läs den nedifrån och upp:

FelUpptäcks avKostar dig
3 och 4KompilatornEn minut. Meddelandet säger vad som är fel
5ServerstartenNågra minuter. Meddelandet är långt men det står där
1 och 2Sidan du öppnadeSå lång tid det tar innan du öppnar just den sidan
6En annan människaKan vara i produktion

Frågan att landa i:

Vilket av de sex felen är farligast? Och varför är det inte det som ser värst ut?

Fel 5 ser mest dramatiskt ut – servern startar inte, utskriften är en vägg av text. Men det stoppar dig direkt och kostar ingenting. Fel 6 ser inte ut som något alls, och är det enda som kan gå obemärkt hela vägen ut till riktiga besökare.

Det är därför två vanor är värda mer än resten i det här området:

Ändrat något i Program.cs? Starta om servern.
Byggt något som håller data? Öppna ett privat fönster.