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.
Guiden är skriven för att gå att följa på egen hand. Så här hänger den ihop med passen:
- Lektion 1 (ons 19 aug) – steg 1 och steg 2. Skapa projektet och rensa mallen.
- Lektion 2 (tis 25 aug) – ingen kod. Läs Klient och server.
- Lektion 3 (ons 26 aug) – ingen kod. Läs Renderingslägen.
- Lektion 4 (tis 1 sept) – Projektets arkitektur först, sedan steg 3 till steg 8. Det är den största biten.
- Lektion 5 (ons 2 sept) – checklistan och inlämningen.
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.
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.
| Webbresan | Ditt projekt |
|---|---|
Projektnamnet Webbresan | ditt projektnamn – ett ord, inga å ä ö |
Exempeladressen https://www.scb.se/hitta-statistik/?fraga=befolkning#resultat | en egen adress som har både frågesträng och fragment |
| Beviset: klockslag + löpnummer | ditt eget bevis, som inte är klockslag + löpnummer |
Sidorna /adressen, /svaret, /arkitekturen | dina egna sidnamn och rubriker |
| Statuskoderna 200, 301, 404, 500 | minst 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
| Begrepp | Vad det är |
|---|---|
| Klient | Programmet som frågar. Oftast webbläsaren. |
| Server | Programmet som svarar. Vår kod. |
| HTTP | Reglerna för hur frågan och svaret ska se ut. Läsbar text. |
| Statuskod | Talet först i svaret som säger hur det gick: 200, 404, 500. |
| DNS | Översätter domännamn till IP-adress. |
| Statisk sida | En färdig fil som skickas som den är. |
| Dynamisk sida | Innehåll som skapas i samma stund som någon frågar. |
| Rendering | Att sätta ihop den färdiga HTML:en. Hos oss sker det på servern. |
| Razor-komponent | En .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
<!DOCTYPE html>...</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å.
<!DOCTYPE html> 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
| Felmeddelande | Vad det betyder | Gör så här |
|---|---|---|
The type or namespace name ... could not be found | Stavfel i en typ, eller filen ligger i fel mapp | Kontrollera att sidor ligger i Components/Pages/ |
| Sidan ger 404 fast filen finns | @page-raden saknas eller har fel adress | Första raden i filen ska vara @page "/adressen" |
Unterminated string literal i en .razor | En " saknas i C#-delen | Titta på raden ovanför den som pekas ut |
Å Ä Ö visas som åäö | Filen är sparad i fel teckenkodning | Spara om som UTF-8 |
| Menyn markerar Start på alla sidor | Match="NavLinkMatch.All" saknas | Lägg till den på startsidans NavLink |
| Ändringen syns inte | Bara filen sparad, inte omstartad | Stoppa 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
- ASP.NET Core Blazor render modes – vad
-int Noneegentligen stängde av, och vad alternativen gör - ASP.NET Core Blazor routing – hur
@pageochRoutes.razorhänger ihop - MDN: HTTP-statuskoder – hela listan, med förklaring av varje