← Område 1 Guide · Webbresan När något går fel

Webbserverprogrammering 1 · område 1 · guide

Webbresan – från adressfält till renderad sida

Hela vägen från tomt projekt till färdig sajt, steg för steg, med all kod. Följer du den här sidan uppifrån och ned hamnar du på samma ställe som klassen gjorde på lektionerna.

Missade du en lektion?

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

Räkna med att steg 3–8 tar längre tid hemma än de 30 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 1 · Webbserverprogrammering 1

Vad du bygger här

En webbplats med fyra sidor som förklarar vad som händer under den halvsekund som går mellan att du trycker enter och att sidan syns. Startsidan bevisar dessutom att den skapades på servern just nu, genom att visa ett klockslag och ett löpnummer som ändras varje gång du laddar om.

Webbresan
├── /               Vad händer när du trycker enter?   + beviset
├── /adressen       URL:ens delar och DNS
├── /svaret         Förfrågan, svar och statuskoder
└── /arkitekturen   Projektets egna filer

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

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

Samma sajt, med ditt eget innehåll. Du bygger fyra sidor som förklarar samma resa, men du väljer själv vilken adress du följer genom förklaringen och hur du bevisar att sidan skapas på servern. Arkitektursidan beskriver ditt eget projekt, inte mitt.

Du är klar när någon som inte läst guiden kan läsa din sajt och förstå vad som händer när de trycker enter.

Översättningstabellen

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

WebbresanDitt projekt
Projektnamnet Webbresanditt projektnamn – ett ord, inga å ä ö
Exempeladressen https://www.scb.se/hitta-statistik/?fraga=befolkning#resultaten egen adress som har både frågesträng och fragment
Beviset: klockslag + löpnummerditt eget bevis, som inte är klockslag + löpnummer
Sidorna /adressen, /svaret, /arkitekturendina egna sidnamn och rubriker
Statuskoderna 200, 301, 404, 500minst fyra koder, minst två av dem andra än mina

Att kopiera min exempeladress fungerar inte: din adress ska ha andra delar att peka på, och du ska kunna förklara varje del i just din.

Innan du börjar

Detta ska fungera efter lektion 1:

dotnet --version

Svaret ska börja med 10.. Får du "command not found" är inte SDK:n installerad – säg till.

Begreppen i det här momentet

BegreppVad det är
KlientProgrammet som frågar. Oftast webbläsaren.
ServerProgrammet som svarar. Vår kod.
HTTPReglerna för hur frågan och svaret ska se ut. Läsbar text.
StatuskodTalet först i svaret som säger hur det gick: 200, 404, 500.
DNSÖversätter domännamn till IP-adress.
Statisk sidaEn färdig fil som skickas som den är.
Dynamisk sidaInnehåll som skapas i samma stund som någon frågar.
RenderingAtt sätta ihop den färdiga HTML:en. Hos oss sker det på servern.
Razor-komponentEn .razor-fil med markup och C# i samma fil.

Steg 1 – Skapa projektet

Vad vi gör. Skapar ett Blazor Web App utan interaktivitet.

Kommandot. Kör i mappen där du vill att projektmappen ska hamna:

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

Varför det ser ut så. -int None betyder ingen interaktivitet. Utan den flaggan får du ett projekt där delar av koden också körs i webbläsaren, och då blir det otydligt vad som händer var. Hela den här kursen håller sig på serversidan: all C# körs på servern, och webbläsaren får färdig HTML. Det gör att du alltid kan svara på frågan "var körs den här raden?" – på servern.

Kontrollera. Terminalen skriver ut en adress, ungefär http://localhost:5199. Öppna den. Du ser en sida med texten "Hello, world!".

I ditt projekt. Byt Webbresan mot ditt projektnamn. Namnet blir också C#-namnrymd, så använd ett ord utan mellanslag och utan å ä ö.

Steg 2 – Rensa bort det du inte behöver

Vad vi gör. Tar bort vädersidan som mallen lägger in, och rättar sidans språkdeklaration.

Filerna. Radera Components/Pages/Weather.razor.

Öppna sedan Components/App.razor. Andra raden ser ut så här:

<html lang="en">

Ändra den till:

<html lang="sv">

Varför det ser ut så. Mallen är gjord för att visa upp ramverket, inte för att vara en bra utgångspunkt. Vädersidan hämtar påhittad data och distraherar. Att rensa bort det du inte förstår är inte fusk – det är förutsättningen för att resten ska gå att överblicka.

lang="en" är samma sak: ett standardvärde från mallen som inte stämmer för din sajt. Attributet talar om vilket språk texten är skriven på. En skärmläsare använder det för att välja uttal – med en läses "Förfrågan" med engelska språkljud. Webbläsaren väljer stavningsordlista med det, och sökmotorer använder det för att veta vad de indexerar.

Blanda inte ihop det med <meta charset="utf-8" /> några rader ned. charset handlar om tecken: hur bytes tolkas, och det är den raden som gör att ö inte blir ö. lang handlar om språk. En sida kan visa Å Ä Ö helt rätt och ändå läsas upp på engelska.

Kontrollera. Sidan startar fortfarande. Menylänken till Weather är kvar och ger fel om du klickar – vi fixar menyn i steg 6. Högerklicka på sidan och välj Visa sidkälla – andra raden ska nu säga lang="sv".

I ditt projekt. Samma sak. Är din sajt på svenska ska det stå sv.

Steg 3 – Startsidan och beviset för serverrendering

Vad vi gör. Skriver om startsidan så att den både förklarar de fem stegen och bevisar att den skapades på servern.

Filen. Components/Pages/Home.razor

@page "/"

<PageTitle>Webbresan – vad händer när du trycker enter?</PageTitle>

<h1>Vad händer när du trycker enter?</h1>

<p>
    Du skriver en adress i webbläsaren och trycker enter. En halv sekund senare
    ligger sidan där. Den här sajten följer de fem stegen som hinner ske under tiden.
</p>

<ol>
    <li>Webbläsaren delar upp <strong>adressen</strong> i sina delar.</li>
    <li><strong>DNS</strong> översätter domännamnet till en IP-adress.</li>
    <li>Webbläsaren skickar en <strong>förfrågan</strong> till servern på den adressen.</li>
    <li>Servern skickar tillbaka ett <strong>svar</strong> med en statuskod och innehåll.</li>
    <li>Webbläsaren <strong>ritar upp</strong> det den fick.</li>
</ol>

<h2>Den här sidan byggdes på servern</h2>

<p class="bevis">
    Sidan skapades <strong>@byggdes.ToString("HH:mm:ss")</strong>
    och fick löpnummer <strong>@nummer</strong>.
</p>

<p>
    Ladda om sidan. Både klockslaget och löpnumret ändras. Ingenting av det står i
    en fil på servern – texten fanns inte innan du bad om sidan.
</p>

@code {
    private static int antalVisningar = 0;

    private DateTime byggdes = DateTime.Now;
    private int nummer;

    protected override void OnInitialized()
    {
        nummer = ++antalVisningar;
    }
}

Varför det ser ut så.

@page "/" är det som gör filen till en sida i stället för en byggsten. Utan den raden går sidan inte att nå på någon adress.

@code { } är C#-delen av komponenten. Allt där körs på servern.

OnInitialized körs en gång innan sidan renderas. Här räknar vi upp och sparar undan numret. Att göra det direkt i markupen hade gett ett nytt värde varje gång uttrycket lästes – här vill vi ha ett värde per sidvisning.

static på antalVisningar gör att värdet överlever mellan förfrågningar. Det är alltså delat av alla besökare samtidigt, vilket är precis fel i en riktig applikation. Här duger det som bevis, och i område 4 återkommer vi till varför det annars är ett problem.

Kontrollera. Ladda om sidan fem gånger. Löpnumret ska gå 1, 2, 3, 4, 5. Högerklicka sedan och välj Visa sidkälla. Talet står som vanlig text i HTML:en – det räknades ut på servern, inte i webbläsaren.

I ditt projekt. Hitta på ett eget bevis. Några som fungerar: serverns tidszon och datum utskrivet på svenska, en slumpad hälsningsfras ur en lista, hur många millisekunder sidan tog att bygga, eller vilken maskin servern kör på (Environment.MachineName). Det ska ändras eller vara omöjligt för webbläsaren att veta.

Steg 4 – Sidan om adressen

Vad vi gör. Bygger en sida som plockar isär en adress i sina delar och förklarar DNS.

Filen. Skapa Components/Pages/Adressen.razor

@page "/adressen"

<PageTitle>Adressen – Webbresan</PageTitle>

<h1>Steg 1 och 2: adressen och DNS</h1>

<h2>Adressen delas upp</h2>

<p>Vi följer den här adressen genom hela sajten:</p>

<p class="adress">https://www.scb.se/hitta-statistik/?fraga=befolkning#resultat</p>

<table class="tabell">
    <caption>URL:ens delar</caption>
    <thead>
        <tr>
            <th scope="col">Del</th>
            <th scope="col">Värde</th>
            <th scope="col">Vad den gör</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var del in delar)
        {
            <tr>
                <th scope="row">@del.Namn</th>
                <td><code>@del.Varde</code></td>
                <td>@del.Beskrivning</td>
            </tr>
        }
    </tbody>
</table>

<h2>DNS översätter namnet</h2>

<p>
    Datorn kan inte skicka något till <code>www.scb.se</code>. Den behöver en
    IP-adress. DNS är telefonkatalogen som gör översättningen, och svaret sparas
    en stund så att nästa besök slipper frågan.
</p>

@code {
    private record Del(string Namn, string Varde, string Beskrivning);

    private readonly Del[] delar =
    [
        new("Protokoll", "https", "Hur samtalet ska föras. s står för krypterat."),
        new("Domännamn", "www.scb.se", "Vilken server som ska svara."),
        new("Sökväg", "/hitta-statistik/", "Vad på servern som efterfrågas."),
        new("Frågesträng", "?fraga=befolkning", "Extra värden till servern."),
        new("Fragment", "#resultat", "Var på sidan webbläsaren ska hoppa. Skickas aldrig till servern.")
    ];
}

Varför det ser ut så.

Innehållet ligger i en array i @code och renderas med @foreach, i stället för att skrivas som fem <tr> för hand. Det är samma sak som du gör i område 2 med List<T>, och det gör att data och presentation hålls isär redan här. Ska du lägga till en rad ändrar du en plats, inte två.

record är en kort form av klass när man bara vill hålla ihop några värden. Du läser mer om klasser i område 2 – här räcker det att veta att Del binder ihop tre strängar.

scope="row" och scope="col" på <th> talar om för skärmläsare vad rubriken gäller. Det kostar inget att göra rätt från början.

Notera raden om fragment: #resultat skickas aldrig till servern. Det är en av de saker som förvånar – webbläsaren behåller den för sig själv.

Kontrollera. Gå till /adressen. Tabellen har fem rader.

I ditt projekt. Använd din egen adress. Den måste ha både frågesträng och fragment – hittar du ingen bra kan du bygga en själv utifrån en sajt du använder. Kontrollera varje rad mot just din adress; kopierar du min beskrivning rakt av kommer den inte att stämma.

Steg 5 – Sidan om förfrågan och svaret

Vad vi gör. Visar hur en HTTP-förfrågan och ett svar faktiskt ser ut, och listar de statuskoder eleverna kommer att möta.

Filen. Skapa Components/Pages/Svaret.razor

@page "/svaret"

<PageTitle>Svaret – Webbresan</PageTitle>

<h1>Steg 3 och 4: förfrågan och svar</h1>

<h2>Förfrågan</h2>

<p>
    Webbläsaren skickar några rader text till servern. Det är hela hemligheten –
    HTTP är läsbar text.
</p>

<pre><code>GET /hitta-statistik/ HTTP/1.1
Host: www.scb.se
Accept: text/html
User-Agent: Mozilla/5.0</code></pre>

<p>
    Första ordet är <strong>metoden</strong>. <code>GET</code> betyder "ge mig",
    <code>POST</code> betyder "här kommer data". Vi använder båda i område 5.
</p>

<h2>Svaret</h2>

<pre><code>HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8

&lt;!DOCTYPE html&gt;...</code></pre>

<p>
    Talet på första raden är <strong>statuskoden</strong>. Den säger hur det gick,
    innan webbläsaren ens tittat på innehållet.
</p>

<table class="tabell">
    <caption>Statuskoder du kommer att se i den här kursen</caption>
    <thead>
        <tr>
            <th scope="col">Kod</th>
            <th scope="col">Betyder</th>
            <th scope="col">När du möter den</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var kod in koder)
        {
            <tr>
                <th scope="row">@kod.Nummer</th>
                <td>@kod.Namn</td>
                <td>@kod.NarDuSerDen</td>
            </tr>
        }
    </tbody>
</table>

<p>
    Prova själv: gå till <a href="/finns-inte">/finns-inte</a> och se vad som händer.
</p>

@code {
    private record Statuskod(int Nummer, string Namn, string NarDuSerDen);

    private readonly Statuskod[] koder =
    [
        new(200, "OK", "Allt gick bra. Den vanligaste koden."),
        new(301, "Flyttad permanent", "Adressen har bytt plats och webbläsaren skickas vidare."),
        new(404, "Hittades inte", "Sökvägen finns inte på servern. Ofta ett stavfel."),
        new(500, "Serverfel", "Din egen kod kraschade. Den här kommer du att se ofta.")
    ];
}

Varför det ser ut så.

&lt;!DOCTYPE html&gt; står med teckenentiteter i stället för < och >. Skriver du tecknen rakt av tolkar Razor dem som HTML och taggen försvinner ur utskriften. Det är samma mekanism som gör Razor säkert mot skadlig inmatning – något du får se ordentligt i område 7.

Länken till /finns-inte är avsiktlig. Den visar 404 på riktigt i stället för att bara påstå att den finns.

Kontrollera. Gå till /svaret, klicka på länken till /finns-inte. Du ska få sidan "Not Found". Öppna sedan nätverksfliken i webbläsaren (F12) och ladda om – statuskoden 404 står i listan.

I ditt projekt. Byt ut minst två av mina fyra statuskoder mot andra. Det finns gott om användbara: 201, 302, 403, 418, 503. Skriv din egen förklaring av när man möter dem.

Steg 6 – Menyn

Vad vi gör. Byter ut mallens meny mot våra fyra sidor.

Filen. Components/Layout/NavMenu.razor – byt ut hela <nav>-elementet och namnet i navbar-brand:

<div class="top-row ps-3 navbar navbar-dark">
    <div class="container-fluid">
        <a class="navbar-brand" href="">Webbresan</a>
    </div>
</div>

<input type="checkbox" title="Navigeringsmeny" class="navbar-toggler" />

<div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()">
    <nav class="nav flex-column">
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">Start</NavLink>
        </div>
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="adressen">Adressen</NavLink>
        </div>
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="svaret">Svaret</NavLink>
        </div>
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="arkitekturen">Arkitekturen</NavLink>
        </div>
    </nav>
</div>

Varför det ser ut så.

NavLink är en komponent, inte en vanlig <a>. Skillnaden är att den själv märker när den pekar på sidan du står på och sätter klassen active. Därför markeras rätt menypost automatiskt.

Match="NavLinkMatch.All" behövs bara på startsidan. Utan den skulle länken till "" räknas som aktiv på alla sidor, eftersom varje adress börjar med /.

Adresserna skrivs utan inledande snedstreck: href="adressen", inte href="/adressen". Det är för att <base href="/" /> i App.razor ska kunna peka om hela sajten om den senare publiceras i en undermapp.

Kontrollera. Klicka runt bland alla fyra sidorna. Rätt post markeras varje gång.

I ditt projekt. Dina sidnamn.

Steg 7 – Arkitektursidan

Vad vi gör. Bygger sidan som förklarar projektets egna filer. Det här är den sida som är mest din egen.

Filen. Skapa Components/Pages/Arkitekturen.razor

@page "/arkitekturen"

<PageTitle>Arkitekturen – Webbresan</PageTitle>

<h1>Steg 5: hur den här sajten är byggd</h1>

<p>
    Sidan du läser är inte en fil som ligger färdig på servern. Den sätts ihop av
    flera filer varje gång någon ber om den. Så här går det till, i ordning.
</p>

<table class="tabell">
    <caption>Filerna i projektet och vad de gör</caption>
    <thead>
        <tr>
            <th scope="col">Ordning</th>
            <th scope="col">Fil</th>
            <th scope="col">Ansvar</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var steg in kedjan)
        {
            <tr>
                <th scope="row">@steg.Ordning</th>
                <td><code>@steg.Fil</code></td>
                <td>@steg.Ansvar</td>
            </tr>
        }
    </tbody>
</table>

<h2>Kedjan i en mening</h2>

<p>
    <code>Program.cs</code> startar servern, <code>App.razor</code> bygger
    HTML-dokumentet, <code>Routes.razor</code> väljer sida utifrån adressen,
    <code>MainLayout.razor</code> lägger ramen runt, och sidan själv fyller i
    innehållet.
</p>

@code {
    private record Steg(int Ordning, string Fil, string Ansvar);

    private readonly Steg[] kedjan =
    [
        new(1, "Program.cs", "Startar webbservern och bestämmer vad den kan svara på."),
        new(2, "Components/App.razor", "Själva HTML-dokumentet: html, head och body."),
        new(3, "Components/Routes.razor", "Matchar adressen mot rätt sida. Ingen träff ger NotFound."),
        new(4, "Components/Layout/MainLayout.razor", "Ramen som alla sidor delar: meny och innehållsyta."),
        new(5, "Components/Pages/Arkitekturen.razor", "Den här sidan. Fyller @Body i layouten."),
        new(6, "wwwroot/app.css", "Utseendet. Skickas som en egen fil till webbläsaren.")
    ];
}

Varför det ser ut så. Ordningen i tabellen är den ordning filerna faktiskt körs i vid en förfrågan. Öppna filerna vid sidan av och läs dem i den ordningen – det är den enda gången under kursen du kommer att göra det, och det är den bästa halvtimme du kan lägga på att förstå ramverket.

Att skriva den här sidan är hela poängen med uppgiften. Du kan inte beskriva filerna utan att ha öppnat dem.

Kontrollera. Gå till /arkitekturen. Öppna sedan var och en av de sex filerna och kontrollera att din beskrivning stämmer.

I ditt projekt. Beskriv dina filer. Har du fler sidor än jag ska de vara med. Skriv beskrivningarna med egna ord efter att ha öppnat filerna – det märks direkt på en arkitektursida om filerna aldrig blivit öppnade.

Steg 8 – Utseendet

Vad vi gör. Lägger till fyra klasser sist i stilmallen.

Filen. wwwroot/app.css, lägg till längst ned:

/* Webbresan */
.adress {
    font-family: monospace;
    font-size: 1.1rem;
    background: #f3f3f3;
    padding: .6rem .8rem;
    border-left: 4px solid #1b6ec2;
    overflow-wrap: anywhere;
}

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

.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;
}

Varför det ser ut så. Stilmallen är en egen fil som webbläsaren hämtar separat. Öppna nätverksfliken och ladda om: du ser app.css som ett eget anrop med egen statuskod. Det är samma resa som HTML-sidan gjorde, en gång till.

Kontrollera. Tabellerna har ramar och beviset på startsidan har blå bakgrund.

I ditt projekt. Egna klassnamn och egna färger om du vill.

När något går fel

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

6 rader

FelmeddelandeVad det betyderGör så här
The type or namespace name ... could not be foundStavfel i en typ, eller filen ligger i fel mappKontrollera att sidor ligger i Components/Pages/
Sidan ger 404 fast filen finns@page-raden saknas eller har fel adressFörsta raden i filen ska vara @page "/adressen"
Unterminated string literal i en .razorEn " saknas i C#-delenTitta på raden ovanför den som pekas ut
Å Ä Ö visas som åäöFilen är sparad i fel teckenkodningSpara om som UTF-8
Menyn markerar Start på alla sidorMatch="NavLinkMatch.All" saknasLägg till den på startsidans NavLink
Ändringen syns inteBara filen sparad, inte omstartadStoppa med Ctrl+C och kör dotnet run igen

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

Löpnumret börjar inte om. Räknaren är static och nollställs bara när servern startas om. Startar du om med Ctrl+C och dotnet run börjar den på 1 igen. Det är väntat, inte ett fel.

Din adress har kanske inte alla delar. Många adresser saknar frågesträng eller fragment. Leta tills du hittar en som har båda, eller välj en sajt med sökfunktion och sök på något – då får du en frågesträng på köpet.

Arkitektursidan blir tunn om du skriver den först. Skriv den sist, när du själv rört alla filerna. Beskrivningar som "den här filen hanterar layouten" utan att ha öppnat filen syns direkt.

Fyra sidor är ett minimum, inte ett tak. Har du något eget att förklara – portar, HTTPS, cache – gör en femte sida.

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