← Område 2 Guide · Filmarkivet När något går fel

Webbserverprogrammering 1 · område 2 · guide

Filmarkivet – C# på serversidan

Sjutton steg som bygger fem sidor: datatyper, villkor, loopar, listor och till sist en tabell renderad ur en lista med filmobjekt. Följer du sidan uppifrån och ned har du sett allt som prövas på provet, och du har mönstret för din egen övningsbank.

Missade du en lektion?

Guiden går att följa på egen hand. Så här hänger stegen ihop med passen:

Steg 14 till 16 är den största biten och tar längre tid hemma än de drygt fyrtio minuter klassen fick. Fastnar du: skriv i itslearning i stället för att vänta till nästa pass.

Koden går inte att kopiera

Kodblocken på den här sidan går inte att markera, och det är med flit. Att skriva av koden för hand är där en stor del av inlärningen sker: du läser raden, håller den i huvudet och upptäcker vid tangentbordet vad du inte förstod. Klistrar du in den hoppar du över precis det steget.

Guide · Område 2 · Webbserverprogrammering 1

Vad du bygger här

Filmarkivet, i sin första version: fem sidor som var och en visar en del av C#. Sidan för lektion 6 handlar om datatyper, sidan för lektion 8 om villkor, och så vidare fram till en tabell som byggs ur en lista med filmobjekt.

Filmarkivet
├── /               Vilken sida hör till vilket pass
├── /datatyper      int, double, bool, string. Interpolation och int.TryParse
├── /villkor        if, else if, else, switch. Villkor i markupen
├── /loopar         for, foreach, while. List<T> renderad som tabell
└── /filmer         Metoder, klassen Film, en lista med objekt

Det färdiga projektet ligger i referensprojekt/Filmarkivet – kör det gärna innan du börjar, så vet du vart du är på väg.

Allt du skriver hamnar på en sida. Det finns inget konsolfönster i den här kursen. C# övas i @code-blocket i en komponent, och resultatet renderas i markupen. Det är samma modell som i område 1: koden körs på servern, webbläsaren får färdig HTML.

Vad du ska göra själv efteråt

Två saker, och de hänger ihop.

Övningsbanken. Fyra sidor i ditt eget projekt, en per tema, där du löser övningarna ur ovningar/omrade-2.md. Tjugo lösta övningar krävs för inlämningen.

Din egen tabell. En modellklass Produkt som du hittar på fälten till, en List<Produkt> med minst fem objekt, och en komponent som renderar dem i en tabell. Samma sak som /filmer gör med filmer, fast med dina fält.

Produkt är med flit en engångsklass. Din riktiga domän – den du bär resten av kursen – väljer du i område 3, och då finns det kriterier att välja efter. Här handlar det om att lära sig klasser, inte om att välja rätt.

Översättningstabellen

Fyll i högerkolumnen innan du börjar koda.

FilmarkivetDitt projekt
Klassen FilmProdukt
Titel (string)ett textfält
Ar (int)ett heltalsfält
LangdMinuter (int)ett heltal du kan räkna på
Betyg (int, 1–5)ett fält med ett begränsat intervall
Tillganglig (bool)ett ja/nej-fält
Metoden Speltiden metod som formaterar ett av dina fält
Metoden Snittbetygen metod som räknar på hela listan
Fem filmer i listanminst fem av dina objekt

Kopierar du mina fält rakt av blir din tabell en avskrift av min. Fälten ska vara dina, och metoderna ska räkna på dem.

Innan du börjar

Du behöver ett projekt att öva i. Skapa det på samma sätt som i område 1:

dotnet new blazor -o Ovningsbanken -int None
cd Ovningsbanken
dotnet run

Du kan också fortsätta i projektet från område 1. Blanda då inte ihop dem: lägg övningssidorna i Components/Pages/ med tydliga namn.

Begreppen i det här momentet

BegreppVad det är
DatatypVad ett värde är för sorts sak: text, heltal, decimaltal, sant/falskt
VariabelEtt namn som håller ett värde
FältEn variabel som hör till komponenten och lever så länge sidan byggs
Interpolation$"..." – att sätta in värden mitt i en text
VillkorEn fråga som besvaras med sant eller falskt
LoopKod som körs flera gånger
ListaEn samling värden i en variabel, List<T>
MetodEtt namngivet kodstycke som tar emot värden och lämnar tillbaka ett svar
KlassEn beskrivning av vad något är: vilka fält det har
ObjektEn sak skapad ur en klass

Steg 1 – Projektet och den första sidan

Vad vi gör. Skapar Filmarkivet och en tom sida att öva på.

Kommandot.

dotnet new blazor -o Filmarkivet -int None
cd Filmarkivet

Radera Components/Pages/Weather.razor och sätt lang="sv" i Components/App.razor, precis som i område 1.

Filen. Skapa Components/Pages/Datatyper.razor:

@page "/datatyper"

<PageTitle>Datatyper – Filmarkivet</PageTitle>

<h1>Lektion 6: datatyper, variabler och utdata</h1>

<p>Här kommer innehållet.</p>

@code {
    private string titel = "Nyckeln till frihet";
}

Varför det ser ut så. @code-blocket är C#-delen av komponenten. Allt där körs på servern, en gång per förfrågan. private betyder att fältet hör till den här komponenten och inte syns utifrån.

Kontrollera. Gå till /datatyper. Rubriken syns.

I ditt projekt. Samma sak, men döp sidan efter temat du övar på: Components/Pages/OvningarTyper.razor med @page "/ovningar/typer".

Steg 2 – De fyra typerna

Vad vi gör. Lägger till ett fält av varje grundtyp och visar dem i en tabell.

Filen. Components/Pages/Datatyper.razor, i @code-blocket:

private string titel = "Nyckeln till frihet";
private int langdMinuter = 142;
private double snittbetyg = 4.6;
private bool tillganglig = true;

Och i markupen:

<table class="tabell">
    <thead>
        <tr>
            <th scope="col">Typ</th>
            <th scope="col">Innehåller</th>
            <th scope="col">Exempel ur den här sidan</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <th scope="row"><code>string</code></th>
            <td>Text</td>
            <td>@titel</td>
        </tr>
        <tr>
            <th scope="row"><code>int</code></th>
            <td>Heltal</td>
            <td>@langdMinuter</td>
        </tr>
        <tr>
            <th scope="row"><code>double</code></th>
            <td>Decimaltal</td>
            <td>@snittbetyg</td>
        </tr>
        <tr>
            <th scope="row"><code>bool</code></th>
            <td>Sant eller falskt</td>
            <td>@tillganglig</td>
        </tr>
    </tbody>
</table>

Varför det ser ut så.

Typen står först i deklarationen: int langdMinuter betyder ett heltal som heter langdMinuter. Typen är inte en formalitet – den bestämmer vad du får göra. Du kan dividera ett int, men inte ett bool.

double är decimaltal. Skriv 4.6 med punkt i koden, oavsett att svenskan skriver komma. Punkt är C#-syntax; kommat dyker upp först när värdet skrivs ut.

Ett @ framför namnet i markupen växlar över till C# och skriver ut värdet. Det är samma @ som i område 1.

Kontrollera. Tabellen visar fyra rader. @tillganglig skrivs ut som True – inte Sant. Det är C#:s eget sätt att skriva ut ett bool.

I ditt projekt. Fyra egna fält, ett av varje typ. Låt dem handla om något du bryr dig om.

Steg 3 – Stränginterpolation

Vad vi gör. Sätter ihop en mening av flera värden.

Filen. Lägg till i @code:

// $ gör strängen interpolerad: allt inom { } räknas ut och sätts in.
private string sammanfattning =>
    $"{titel} är {langdMinuter} minuter lång och har betyg {snittbetyg}.";

Och i markupen:

<p class="ruta">@sammanfattning</p>

Varför det ser ut så.

$ före citattecknet är det som gör interpolationen möjlig. Utan det skrivs {titel} ut ordagrant, klammerparenteser och allt.

=> i stället för = betyder att värdet räknas ut varje gång det används, inte en gång när sidan börjar byggas. Här spelar det ingen roll, men det gör det så fort något kan ändras – och det gör att raden går att läsa som sammanfattning är det här, vilket den ju är.

Alternativet är att slå ihop text med +: titel + " är " + langdMinuter + " minuter lång". Det fungerar, men blir svårläst så fort det är mer än två delar. Interpolation finns för att slippa det.

Kontrollera. Meningen syns med rätt värden insatta.

I ditt projekt. En mening som sätter ihop minst tre av dina fält.

Steg 4 – Räkna med heltal

Vad vi gör. Gör om minuter till timmar och minuter.

Filen. Lägg till i @code:

private int timmar => langdMinuter / 60;   // heltalsdivision: 142 / 60 = 2
private int minuter => langdMinuter % 60;  // resten: 142 % 60 = 22

Och i markupen:

<p>
    Filmen är @langdMinuter minuter lång, alltså @timmar timmar och @minuter minuter.
</p>

Varför det ser ut så.

142 / 60 blir 2, inte 2.3667. När båda talen är int blir svaret också int, och decimalerna kastas bort. Det är inte avrundning – 2.9 blir 2.

% ger resten efter divisionen. 142 % 60 är 22, alltså de minuter som blir över efter två hela timmar. Operatorn heter modulo och är förvånansvärt användbar: den avgör också om ett tal är jämnt, med tal % 2 == 0.

Vill du ha decimalerna måste minst ett av talen vara double: (double)langdMinuter / 60 ger 2.3667.

Kontrollera. Sidan säger 2 timmar och 22 minuter.

I ditt projekt. Räkna på ditt heltalsfält. Har du ett pris i ören: gör om det till kronor och ören med samma två operatorer.

Steg 5 – Från text till tal

Vad vi gör. Tolkar text som ett tal, utan att sidan kraschar när det inte går.

Filen. Lägg till i @code:

// int.TryParse returnerar true eller false i stället för att krascha.
// Lyckas den läggs det tolkade värdet i variabeln efter out.
private string ForsokTolka(string text)
{
    if (int.TryParse(text, out int tal))
    {
        return $"\"{text}\" gick att tolka som talet {tal}.";
    }

    return $"\"{text}\" gick inte att tolka som ett tal.";
}

Och i markupen:

<ul>
    <li>@ForsokTolka("1994")</li>
    <li>@ForsokTolka("nittiofyra")</li>
</ul>

Varför det ser ut så.

Text som ser ut som ett tal är inte ett tal. "1994" är fyra tecken. Ska du räkna med värdet måste det konverteras först.

int.Parse("nittiofyra") kraschar. int.TryParse gör det inte – den lämnar tillbaka false och låter dig hantera fallet. Det är skillnaden mellan en sida som går sönder för en besökare och en sida som svarar vänligt.

out int tal deklarerar variabeln mitt i anropet. Lyckas tolkningen ligger talet där efteråt.

\" inuti en sträng är ett citattecken som inte avslutar strängen. Snedstrecket säger det här tecknet är data, inte syntax.

Kontrollera. Första raden lyckas, andra misslyckas. Ingen krasch.

I ditt projekt. Samma metod, men med ett värde som är rimligt i din domän.

Detta är en metod. Vi går igenom metoder ordentligt i steg 10 – här räcker det att se att den tar emot ett värde och lämnar tillbaka ett annat.

Steg 6 – Villkor

Vad vi gör. Låter sidan svara olika beroende på vad klockan är.

Filen. Skapa Components/Pages/Villkor.razor:

@page "/villkor"

<PageTitle>Villkor – Filmarkivet</PageTitle>

<h1>Lektion 8: villkor och logik</h1>

<p class="ruta">@Halsning()</p>

<p>
    Klockan på servern är @nu.ToString("HH:mm"). Ladda om sidan om en timme och
    hälsningen blir en annan – villkoret prövas på nytt vid varje förfrågan.
</p>

@code {
    private DateTime nu = DateTime.Now;

    private string Halsning()
    {
        if (nu.Hour < 12)
        {
            return "God förmiddag.";
        }
        else if (nu.Hour < 18)
        {
            return "God eftermiddag.";
        }
        else
        {
            return "God kväll.";
        }
    }
}

Varför det ser ut så.

Ordningen spelar roll. nu.Hour < 12 prövas först, och är den sann prövas inget mer. Hade < 18 stått först hade < 12 aldrig kunnat bli sann – alla timmar under 12 är också under 18.

else if är inte samma sak som två separata if. Med två if prövas båda, och båda kan slå till.

Det här är samma princip som beviset i område 1: koden körs på servern vid varje förfrågan, så svaret kan skilja mellan två sidvisningar.

Kontrollera. Hälsningen stämmer med klockan. Ändra tillfälligt nu.Hour till ett fast tal och ladda om för att pröva de andra grenarna.

I ditt projekt. Ett villkor som svarar olika beroende på ett av dina fält.

Steg 7 – Villkor i markupen

Vad vi gör. Låter villkoret bestämma vilken HTML som skapas, inte bara vilken text som skrivs ut.

Filen. Lägg till i markupen i Villkor.razor:

@if (nu.Hour >= 18)
{
    <p class="ruta">Det är kväll. Perfekt tid för en lång film.</p>
}
else if (nu.Hour >= 12)
{
    <p class="ruta">Eftermiddag. Något kort kanske?</p>
}
else
{
    <p class="ruta">Förmiddag. Filmarkivet är öppet ändå.</p>
}

Varför det ser ut så.

Skillnaden mot steg 6: där valde villkoret en text, här väljer det vilken HTML som byggs. Grenen som inte tas skickas aldrig till webbläsaren – titta i sidkällan, det finns bara ett <p> där.

@if i markupen behöver klammerparenteser runt HTML:en. else if och else skrivs utan @ – växeln till C# gjordes redan av det första @if.

Kontrollera. Visa sidkällan. Bara en av de tre paragraferna finns i det som skickades.

I ditt projekt. En @if som visar olika HTML beroende på ett av dina fält.

Steg 8 – Ett värde ur adressen

Vad vi gör. Låter sidan svara på ett värde besökaren skriver i adressen.

Filen. Lägg till i @code:

// Värdet hämtas ur frågesträngen. Saknas det blir Betyg 0.
[SupplyParameterFromQuery]
public int Betyg { get; set; }

private string BetygIOrd(int betyg)
{
    if (betyg == 0)
    {
        return "Inget betyg angivet. Lägg till ?betyg=4 i adressen.";
    }

    if (betyg < 1 || betyg > 5)
    {
        return $"{betyg} är inget giltigt betyg. Skalan går från 1 till 5.";
    }

    return $"Betyg {betyg} av 5.";
}

Och i markupen:

<p class="ruta">@BetygIOrd(Betyg)</p>

Varför det ser ut så.

Det här är frågesträngen från område 1, nu använd på riktigt. ?betyg=4 sist i adressen, och värdet hamnar i egenskapen. [SupplyParameterFromQuery] är raden som kopplar ihop dem.

Egenskapen är public, inte private. Ramverket ska kunna sätta den utifrån, och då måste den vara synlig utifrån.

Saknas värdet blir Betyg noll – ett int kan inte vara tomt. Därför prövas noll först och betyder inget angivet.

|| betyder eller. betyg < 1 || betyg > 5 är sant om något av dem stämmer.

Kontrollera. Prova /villkor, /villkor?betyg=4 och /villkor?betyg=9. Tre olika svar.

I ditt projekt. Läs ett eget värde ur adressen och svara olika på det.

Steg 9 – switch

Vad vi gör. Byter en lång kedja av else if mot något läsbarare.

Filen. Lägg till i @code:

private string Omdome(int betyg) => betyg switch
{
    5 => "Mästerverk",
    4 => "Mycket bra",
    3 => "Sevärd",
    2 => "Tveksam",
    1 => "Undvik",
    _ => "Okänt betyg"
};

Och i markupen:

<ul>
    <li>Betyg 5: @Omdome(5)</li>
    <li>Betyg 3: @Omdome(3)</li>
    <li>Betyg 1: @Omdome(1)</li>
</ul>

Varför det ser ut så.

Fem else if efter varandra går att skriva, men ögat måste läsa hela kedjan för att hitta ett fall. En switch ställer upp alternativen i en lista.

_ är allt annat. Den måste finnas: C# kräver att varje möjligt värde har ett svar, och int kan vara mycket annat än 1 till 5.

Formen betyg switch { ... } är ett switch-uttryck: det ger ett värde. Det finns också en äldre form med case och break som du kommer att se när du googlar. Den gör samma sak, med fler rader.

Kontrollera. Tre olika omdömen i listan.

I ditt projekt. En switch över ett fält som har ett fåtal möjliga värden.

Steg 10 – Loopar och listor

Vad vi gör. Håller flera värden i en variabel och går igenom dem.

Filen. Skapa Components/Pages/Loopar.razor:

@page "/loopar"

<PageTitle>Loopar och listor – Filmarkivet</PageTitle>

<h1>Lektion 10: loopar, listor och arrayer</h1>

<p>
    Listan innehåller @genrer.Count värden. Det första ligger på plats
    <code>[0]</code>, inte <code>[1]</code>: @genrer[0].
</p>

<ul>
    @foreach (var genre in genrer)
    {
        <li>@genre</li>
    }
</ul>

<ol>
    @for (int i = 0; i < genrer.Count; i++)
    {
        <li>Plats @i i listan: @genrer[i]</li>
    }
</ol>

@code {
    private List<string> genrer = new() { "Drama", "Thriller", "Komedi", "Dokumentär" };
}

Varför det ser ut så.

List<string> betyder en lista med strängar. Det inom vinkelparenteserna är vad listan innehåller. new() { ... } skapar listan och fyller den direkt.

Count är antalet. Inte Length, som gäller arrayer och strängar.

Listan börjar på plats noll. genrer[0] är den första. Det känns fel första gången och naturligt efter en vecka.

foreach går igenom alla värden och du slipper räknaren. for ger dig räknaren, vilket behövs när numret ska synas eller när du vill gå baklänges. Välj foreach när du bara vill åt värdena.

Kontrollera. Fyra punkter i den första listan, fyra numrerade i den andra.

I ditt projekt. En lista med minst fyra av dina egna värden, renderad två gånger – en gång med foreach, en gång med for.

Steg 11 – Räkna medan du går

Vad vi gör. Samlar ihop ett svar ur alla värdena i en lista.

Filen. Lägg till i Loopar.razor:

private List<int> speltider = new() { 142, 96, 175, 88, 120 };

private int SummeraForHand()
{
    int summa = 0;

    foreach (var tid in speltider)
    {
        summa = summa + tid;
    }

    return summa;
}

Och i markupen:

<p class="ruta">
    Sammanlagd speltid: @speltider.Sum() minuter fördelat på @speltider.Count filmer.
    Den längsta är @speltider.Max() minuter.
</p>

<p>
    Samma sak räknat för hand med en loop, så att du ser vad <code>Sum</code> gör:
    @SummeraForHand() minuter.
</p>

Varför det ser ut så.

Sum() och Max() finns färdiga. Men skriv loopen en gång ändå: det är den som visar vad som händer, och det är den formen du behöver så fort uppgiften är något annat än summa eller max.

int summa = 0; måste stå utanför loopen. Står den innanför nollställs den varje varv och svaret blir det sista värdet i stället för summan. Det är det vanligaste felet i hela området.

Kontrollera. Båda raderna säger 621 minuter.

I ditt projekt. Räkna ihop något ur din lista, både med en färdig metod och med en egen loop.

Steg 12 – while

Vad vi gör. En loop som körs tills något är sant, utan att veta hur många varv det blir.

Filen. Lägg till i Loopar.razor:

private string Halveringar(int tal)
{
    int antal = 0;
    double varde = tal;

    while (varde >= 1)
    {
        varde = varde / 2;
        antal++;
    }

    return $"{tal} kan halveras {antal} gånger innan det blir mindre än 1.";
}

Och i markupen:

<p class="ruta">@Halveringar(142)</p>

Varför det ser ut så.

for och foreach vet hur många varv det blir innan de börjar. while gör inte det – den prövar villkoret före varje varv och slutar när det blir falskt.

varde är double, inte int. Med int hade 1 / 2 blivit 0 direkt, och loopen hade slutat ett varv för tidigt. Typvalet är inte en detalj.

antal++ är kortform för antal = antal + 1.

**Om du glömmer att ändra varde inuti loopen blir villkoret aldrig falskt.** Då kör den för evigt, och sidan svarar aldrig. Avbryt med Ctrl+C i terminalen.

Kontrollera. Sidan säger 8 gånger.

I ditt projekt. En while som räknar något i din domän.

Steg 13 – En tabell ur en lista

Vad vi gör. Låter loopen bygga tabellraderna.

Filen. Lägg till i markupen i Loopar.razor:

<table class="tabell">
    <caption>Speltider</caption>
    <thead>
        <tr>
            <th scope="col">#</th>
            <th scope="col">Minuter</th>
            <th scope="col">Längd</th>
        </tr>
    </thead>
    <tbody>
        @for (int i = 0; i < speltider.Count; i++)
        {
            <tr>
                <th scope="row">@(i + 1)</th>
                <td>@speltider[i]</td>
                <td>@(speltider[i] >= 120 ? "Lång" : "Normal")</td>
            </tr>
        }
    </tbody>
</table>

Varför det ser ut så.

Det här är hela poängen med loopar i en webbapplikation. Fem rader HTML skrivna som en. Kommer det en sjätte film ändrar du listan, inte tabellen.

@(i + 1) behöver parenteser. Utan dem hade @i skrivits ut och + 1 blivit vanlig text. Parenteserna säger hela det här är C#.

villkor ? a : b är en kortform för if–else som ger ett värde. Läs den som om villkoret är sant, a, annars b.

scope="row" och scope="col" talar om för skärmläsare vad rubriken gäller.

Kontrollera. Fem rader i tabellen. Lägg till ett värde i listan och ladda om – sex rader, utan att du rört tabellen.

I ditt projekt. Samma tabell, dina värden.

Steg 14 – Metoder

Vad vi gör. Flyttar uträkningar ut ur markupen.

Filen. Skapa Components/Pages/Filmer.razor och börja med två metoder:

// En metod tar emot värden, gör något, och lämnar tillbaka ett svar.
private string Speltid(int minuter) => $"{minuter / 60} h {minuter % 60} min";

private string Stjarnor(int betyg) => new string('*', betyg);

Varför det ser ut så.

En metod har fyra delar: vem som får se den (private), vad den lämnar tillbaka (string), vad den heter (Speltid), och vad den tar emot (int minuter).

Metodnamn skrivs i PascalCase, med stor bokstav. Variabler och parametrar i camelCase, med liten. Det är C#-praxis, och det är ett krav i uppgiften.

=> i stället för klamrar fungerar när metoden är ett enda uttryck. Behöver den flera rader används { } och return.

new string('*', betyg) skapar en sträng med tecknet upprepat. Enkla citattecken runt '*' betyder ett enskilt tecken, char, inte en sträng.

Vinsten: markupen slipper räkna. @Speltid(film.LangdMinuter) säger vad som menas; @(film.LangdMinuter / 60) h @(film.LangdMinuter % 60) min gör inte det.

Kontrollera. Metoderna kompilerar. De används i nästa steg.

I ditt projekt. Två metoder som formaterar var sitt av dina fält.

Steg 15 – Klassen

Vad vi gör. Håller ihop det som hör ihop.

Filen. Skapa Models/Film.cs:

namespace Filmarkivet.Models;

// En vanlig C#-klass. Inga attribut och ingen databas ännu – det kommer i
// område 5. Här är den bara ett sätt att hålla ihop värden som hör samman.
public class Film
{
    public string Titel { get; set; } = "";
    public int Ar { get; set; }
    public int LangdMinuter { get; set; }
    public int Betyg { get; set; }
    public bool Tillganglig { get; set; }
}

Varför det ser ut så.

Fem filmer betyder fem titlar, fem årtal, fem speltider och fem betyg. Det går att hålla i fyra separata listor – tills något hamnar i otakt och film nummer tre får film nummer fyras betyg. En klass gör den sortens fel omöjligt.

Klassen skapar ingen film. Den säger vilka fält varje film har.

{ get; set; } gör fältet till en egenskap: något som går att läsa och sätta utifrån. Det är formen du kommer att se överallt i C#.

= "" ger Titel ett tomt startvärde. Utan det varnar kompilatorn för att egenskapen kan vara null. Talen behöver det inte – ett int är noll från början.

Klassen ligger i Models/, inte bland sidorna. En modellklass är inte en sida.

namespace Filmarkivet.Models; säger var klassen hör hemma. Sidan som ska använda den behöver då @using Filmarkivet.Models.

Kontrollera. Projektet kompilerar. Klassen används i nästa steg.

I ditt projekt. Models/Produkt.cs med minst fem egenskaper: minst en text, minst ett heltal, och minst ett fält som varken är text eller heltal.

Steg 16 – Objekten och tabellen

Vad vi gör. Skapar objekt ur klassen och renderar dem.

Filen. Components/Pages/Filmer.razor, hela:

@page "/filmer"
@using Filmarkivet.Models

<PageTitle>Filmer – Filmarkivet</PageTitle>

<h1>Lektion 12: metoder, klasser och objekt</h1>

<p>
    Fem filmer betyder fem titlar, fem årtal, fem speltider och fem betyg. Att hålla
    dem i separata listor fungerar tills något hamnar i otakt. En <strong>klass</strong>
    håller ihop det som hör ihop.
</p>

<h2>Klassen</h2>

<p>
    <code>Models/Film.cs</code> beskriver vad en film <em>är</em>. Den skapar ingen
    film – den säger vilka fält varje film har.
</p>

<h2>Objekten</h2>

<p>
    <code>new Film { ... }</code> skapar ett objekt ur klassen. Listan håller
    @filmer.Count stycken, och varje objekt bär alla sina fält samlade.
</p>

<table class="tabell">
    <caption>Filmarkivet</caption>
    <thead>
        <tr>
            <th scope="col">Titel</th>
            <th scope="col">År</th>
            <th scope="col">Längd</th>
            <th scope="col">Betyg</th>
            <th scope="col">Status</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var film in filmer)
        {
            <tr>
                <th scope="row">@film.Titel</th>
                <td>@film.Ar</td>
                <td>@Speltid(film.LangdMinuter)</td>
                <td>@Stjarnor(film.Betyg)</td>
                <td>@(film.Tillganglig ? "Tillgänglig" : "Utlånad")</td>
            </tr>
        }
    </tbody>
</table>

<h2>Metoder som räknar på samlingen</h2>

<ul>
    <li>Snittbetyg: @Snittbetyg().ToString("0.0")</li>
    <li>Längsta filmen: @LangstaFilmen().Titel, @LangstaFilmen().LangdMinuter minuter</li>
    <li>Tillgängliga just nu: @AntalTillgangliga() av @filmer.Count</li>
</ul>

@code {
    private List<Film> filmer = new()
    {
        new Film { Titel = "Nyckeln till frihet", Ar = 1994, LangdMinuter = 142, Betyg = 5, Tillganglig = true },
        new Film { Titel = "Sju samurajer", Ar = 1954, LangdMinuter = 207, Betyg = 5, Tillganglig = false },
        new Film { Titel = "Rosemarys baby", Ar = 1968, LangdMinuter = 137, Betyg = 4, Tillganglig = true },
        new Film { Titel = "Såsom i en spegel", Ar = 1961, LangdMinuter = 89, Betyg = 4, Tillganglig = true },
        new Film { Titel = "Gökboet", Ar = 1975, LangdMinuter = 133, Betyg = 5, Tillganglig = false }
    };

    // En metod tar emot värden, gör något, och lämnar tillbaka ett svar.
    private string Speltid(int minuter) => $"{minuter / 60} h {minuter % 60} min";

    private string Stjarnor(int betyg) => new string('*', betyg);

    private double Snittbetyg()
    {
        int summa = 0;

        foreach (var film in filmer)
        {
            summa = summa + film.Betyg;
        }

        return (double)summa / filmer.Count;
    }

    private Film LangstaFilmen()
    {
        Film langst = filmer[0];

        foreach (var film in filmer)
        {
            if (film.LangdMinuter > langst.LangdMinuter)
            {
                langst = film;
            }
        }

        return langst;
    }

    private int AntalTillgangliga()
    {
        int antal = 0;

        foreach (var film in filmer)
        {
            if (film.Tillganglig)
            {
                antal++;
            }
        }

        return antal;
    }
}

Varför det ser ut så.

new Film { Titel = "...", ... } skapar ett objekt och sätter egenskaperna direkt. Klassen är ritningen, objektet är saken.

List<Film> är en lista med filmobjekt, precis som List<string> var en lista med strängar. Samma form, annat innehåll.

film.Titel – punkten läser en egenskap ur objektet. @foreach (var film in filmer) ger dig ett objekt i taget, och inuti loopen kommer du åt alla dess fält samlade.

(double)summa / filmer.Count – utan (double) hade det blivit heltalsdivision och snittet 4 i stället för 4,6. Samma fälla som i steg 4.

LangstaFilmen returnerar en Film, inte en string. En metod kan lämna tillbaka vad som helst, också ett objekt.

ToString("0.0") avrundar till en decimal vid utskrift.

Kontrollera. Fem rader i tabellen. Snittbetyg 4,6. Längsta filmen Sju samurajer, 207 minuter. Tillgängliga 3 av 5.

I ditt projekt. Minst fem objekt av din klass, renderade i en tabell, plus minst en metod som räknar på hela listan. Det är den delen av inlämningen som inte är övningar.

Steg 17 – Menyn och utseendet

Vad vi gör. Kopplar in sidorna och gör tabellerna läsbara.

Filen. Components/Layout/NavMenu.razor – en post per sida, precis som i område 1:

<div class="nav-item px-3">
    <NavLink class="nav-link" href="datatyper">Datatyper</NavLink>
</div>

Filen. wwwroot/app.css, sist:

/* Filmarkivet */
.tabell {
    border-collapse: collapse;
    width: 100%;
    margin: 1rem 0;
}

.tabell caption {
    text-align: left;
    font-weight: 600;
    padding-bottom: .4rem;
}

.tabell th,
.tabell td {
    border: 1px solid #d0d0d0;
    padding: .4rem .6rem;
    text-align: left;
    vertical-align: top;
}

.tabell thead th {
    background: #f3f3f3;
}

.ruta {
    background: #eef6ff;
    border: 1px solid #b9d8ff;
    padding: .8rem 1rem;
}

Kontrollera. Alla sidor nås från menyn och tabellerna har ramar.

I ditt projekt. Dina fyra övningssidor plus tabellsidan i menyn.

När något går fel

Slå upp felmeddelandet här innan du fastnar. Skriv en bit av det du ser.

9 rader

FelmeddelandeVad det betyderGör så här
CS0102: already contains a definition for 'x'Två övningar på samma sida använder samma variabelnamnDöp om efter övningsnumret: o3Summa i stället för summa
CS0029: Cannot implicitly convert type 'string' to 'int'Du tilldelar text till ett heltalAntingen fel typ på variabeln, eller så behövs int.TryParse
CS0161: not all code paths return a valueEn gren i din metod saknar returnVarje väg genom metoden måste lämna tillbaka något
CS0165: Use of unassigned local variableVariabeln deklarerad men aldrig given ett värdeint summa = 0;, inte bara int summa;
Divisionen blir 0 eller felHeltalsdivisionGör om ett av talen till double med (double)
Summan blir samma som sista värdetsumma nollställs inuti loopenFlytta deklarationen utanför loopen
Sidan svarar aldrig, terminalen hängerOändlig while-loopCtrl+C. Kontrollera att villkoret kan bli falskt
The name 'Film' does not existSidan hittar inte modellklassenLägg till @using Filmarkivet.Models högst upp
Tabellen visar True i stället för JaSå skriver C# ut ett boolAnvänd @(varde ? "Ja" : "Nej")

Det som är lätt att missa när du gör det själv

Heltalsdivision biter två gånger. Först när du räknar timmar, sedan när du räknar snitt. Båda gångerna ser koden rätt ut och svaret är fel. Får du ett misstänkt jämnt tal: kontrollera typerna.

Variabelnamn krockar när övningarna delar sida. Alla @code-block på en sida blir samma klass. Prefixa med övningsnumret från början, så slipper du döpa om tolv variabler i vecka fyra.

Ett fel stoppar hela sidan. Kompilerar inte övning 17 går inte övning 1–16 att visa heller. Det är därför övningarna ligger på fyra sidor och inte en. Får du plötsligt en tom sida: läs det första felet i terminalen.

**var är inte en typ.** var summa = 0; betyder fortfarande int. Kompilatorn räknar ut typen ur värdet, men typen finns där. var gör koden kortare, inte lösare.

Namngivningen är ett krav. PascalCase på metoder och egenskaper, camelCase på variabler och parametrar. Det står i uppgiften och det bedöms.

Checklista

Guidens egen checklista. Den är inte betygskraven – de står i uppgiften och är uppdelade på E, C och A. Den här listan säger bara om du gjort klart det guiden går igenom.

Klart när allt är ikryssat

Kryssen sparas i din webbläsare.

Vill du mer