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.
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.
| Felmeddelandet | Orsaken |
|---|---|
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.
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.
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.
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:
- Öppna
/fel-di/6och tryck på knappen två gånger. Två rader. - Ö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.
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:
| Fel | Upptäcks av | Kostar dig |
|---|---|---|
| 3 och 4 | Kompilatorn | En minut. Meddelandet säger vad som är fel |
| 5 | Serverstarten | Några minuter. Meddelandet är långt men det står där |
| 1 och 2 | Sidan du öppnade | Så lång tid det tar innan du öppnar just den sidan |
| 6 | En annan människa | Kan 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.