Aan het eind van het college komt de code op Blackboard.
In de opdracht breiden we de code uit.
Tot nu toe: LINQ 'achter elkaar' geplakt. Nu: ook tijdelijk in variabelen opslaan.
Beide werken met deferred execution
IQueryable implementeert IEnumerable
Voorbeeld:
IEnumerable<Student> first = db.Students;
var second = first.Where(s => s.Naam == "Bob");
var third = second.Where(s => s.Id > 100);
var result = third.Where(s => s.Id < 200);
Console.Write(result.First().Naam);
Er wordt SELECT * FROM Students uitgevoerd. Verander IEnumerable in IQueryable en er wordt SELECT TOP 1 * FROM Students WHERE Naam = "Bob" AND Id > 100 AND AND Id < 200 uitgevoerd.
verschillende properties van een Student (tegelijkertijd?)
sorteren kan alfabetisch of in omgekeerde volgorde (OrderByDescending)
In deze demo: alleen sorteren op Naam.
Er moeten sorteeropties van de client naar de server.
In de argumenten van de action in de Controller (via query parameters (href) of post data (form))
In de View (argument had ook een boolean kunnen zijn, maar misschien in de toekomst wordt dit naam_oplopend):
<a href="/Students?sorteerVolgorde=oplopend">↑</a>
<a href="/Students?sorteerVolgorde=aflopend">↓</a>
Iets beter (Anchor Tag Helpers):
<a asp-action="Index" asp-route-sorteerVolgorde="oplopend">
In de Controller:
public async Task<IActionResult> Index(string sorteerVolgorde)
Pas op: ViewData["..."] == "....." checkt alleen op reference, doe een cast. s
@if ((string)ViewData["Sorteer"] == "aflopend")
{ <a asp-action="Index" asp-route-sorteerVolgorde="oplopend">↑</a> }
else
{ <a asp-action="Index" asp-route-sorteerVolgorde="aflopend">↓</a> }
In de form met de zoekopdracht, reset de pagina.
<form asp-action="Index" method="get">
<input type="hidden" name="sorteerVolgorde" value='@ViewData["Sorteer"]' />
<input type="hidden" name="pagina" value='0' />
Filter: <input type="text" name="filter" />
<input type="submit" value="Filter" />
</form>
In de link voor de sorteervolgorde, reset de pagina.
<a asp-action="Index"
asp-route-pagina="0"
asp-route-sorteerVolgorde="oplopend"
asp-route-filter='ViewData["Filter"]'>↑</a>
Op een gegeven moment is de lijst leeg... Doorklikken hoort niet te kunnen...
We kunnen extra ViewData toevoegen, maar nu maken we liever een nieuw Model aan.
We maken een speciaal soort lijst:
public class GepagineerdeList<T> : List<T>
{
public int Pagina { get; private set; }
public int PaginaAantal { get; private set; }
public GepagineerdeList(List<T> lijstDeel, int totaalAantal, int pagina, int perPagina)
{
Pagina = pagina;
PaginaAantal = (int)Math.Ceiling (totaalAantal / (double)perPagina);
this.AddRange(lijstDeel);
}
public bool HeeftVorige() { return Pagina > 0; }
public bool HeeftVolgende() { return Pagina < PaginaAantal - 1; }
}
De Pagineer methode wordt een statische methode...
(Constructors kunnen niet async zijn)
public static async Task<GepagineerdeList<T>> CreateAsync(
IQueryable<T> lijst, int pagina, int perPagina)
{
return new GepagineerdeList<T>(
await lijst.Skip(pagina * perPagina).Take(perPagina).ToListAsync(),
await lijst.CountAsync(),
pagina,
perPagina);
}
En in de action:
public async Task<IActionResult> Index(string sorteerVolgorde, string filter, int pagina)
{
return View(await GepagineerdeList<Student>.CreateAsync(Filter(Sort(sorteerVolgorde), filter), pagina, 3));
}
Verwijder alle ViewData!
Gebruik .FirstOrDefault() om een element of leeg element te pakken, of
maak een IEnumerable van het Model en cast daarna waar nodig.
Geef alle data door aan de gebruiker, en blader client-side door de data heen (nadeel=performance, voordeel=performance)
Maak een 'sessie' aan server-side zodra de gebruiker een zoekopdracht doet om te voorkomen dat de resultaten verschuiven tijdens het bladeren
public class Klas
{
public int Id { get; set; }
public string Naam { get; set; }
}
public class Student
{
public int Id { get; set; }
public string Naam { get; set; }
public Klas Klas { get; set; }
}
2x scaffolden! Migraties runnen en database updaten.
Helaas wordt de relatie niet helemaal gescaffold
Wat gebeurt er als Klas Required is? Probeer: voeg toe:
public int KlasId { get; set; }
Een error bij Update-Database? Haal de student weer weg.
Nu een error bij het toevoegen van de student... De Klas moet geset worden.
In de Students/Create
In de View:
<select asp-for="KlasId"
asp-items="@(new SelectList((IEnumerable<Klas>)ViewData["Klassen"], "Id", "Naam"))">
<option>Kies een klas</option>
</select>
In de Action:
[Bind("Id,Naam,KlasId")]
Voeg ook een kolom toe in de Students/Index.
In de Klas/Details:
In de View:
@foreach (Student s in (IEnumerable<Student>)ViewData["Studenten"])
{
<a asp-controller="Klas"
asp-action="VerwijderStudent"
asp-route-student="@s.Id">
Verwijder @s.Naam</a>
}
Maak de action VerwijderStudent aan.
Pas op! Dit is een link, dus wordt deze opnieuw uitgevoerd als de pagina wordt ververst. Gebruik liever PRG.
Alternatieve oplossing: i.p.v. bij elke aanpassing een request, verwerk alleen de aanpassingen aan het eind.
De totale breedte, min de inhoud, is 2 + 128 + 2 + 2 * (8 + 32 + 16)=244
Lesdoel: je kunt begijpt wat semantische elementen zijn en kunt HTML elementen in content categorieën indelen.
De header tag is er niet om stijl aan te geven, maar is er voor semantiek. Screenreaders en bots 'begrijpen' dat dit de header is. Het ziet er niet anders uit.
Lesdoel: je kent de CSS at-regel @media en begrijp je hoe je daarvan gebruik kunt maken om de website op verschillende devices aantrekkelijk te blijven tonen.
Lesdoel: je kunt de breedte van een element beïnvloeden door in CSS gebruik te maken van relatieve width of max-width.
De volgende code
@media screen and (min-width: 500px) { body { width: 500px; } }
@media screen and (max-width: 500px) { body { width: 100%; } }
Lesdoel: Je begrijpt waarom SCSS bestaat en wat de relatie is met CSS.
Performance / compatibilitiet
Property overriden kan. Voorbeeld van toepassing: de setter van achternaam (uit Persoon) van een GetrouwdPersoon verandert ook de achternaam van de partner.
Fout: een interface kan je niet instantieren
Fout: een List<Base> is niet een speciaal soort List<Derived>
Fout: een List<Derived> is niet een speciaal soort List<Base>
Goed
Runtimefout.
var p = personen.LastOrDefault (p => p.Naam == "Jan" && p.Leeftijd < 18)
Error als er niet precies 1 is.
persoon => new { NaamLengte = persoon.Naam.Length }
Er zijn geen compiletime fouten.
ToList() uitvoeren voordat Take wordt uitgevoerd is traag, omdat Select en take lazy zijn, en tolist breekt deze lazyness.
Alleen 1 is een lijst, de rest is een IEnumerable.