Een Shared Layout moet starten met de regel <!DOCTYPE html>
Dit is bij een normale HTML-file altijd de eerste regels (volgens de HTML-syntax)
In de head kun je links naar stylesheets en javascript-bestanden toevoegen
De content (die wordt aangemaakt in je View) wordt toegevoegd op de plek van @RenderBody()
De Views die je zelf maakt, zijn dus geen zelfstandig HTML document
en beginnen dus ook niet met <!DOCTYPE html>
Check met F12 de DOM van het uiteindelijke HTML-document
Daar zie je dat de HTML in je View is toegevoegd op de plek van @RenderBody()
In de Shared Layout kun je ViewData gebruiken:
Bijvoorbeeld om de titel van de pagina in de header te tonen
Of om een variabele in de header of in de footer te tonen
De je Action-method van de Controller moet je deze ViewData dan natuurlijk wel vullen!
Het hele idee van een Shared Layout is het voorkomen van dubbele HTML in de diverse Views
Dubbele HTML-code is namelijk slecht onderhoudbaar omdat elke wijziging op meerdere plekken moet worden doorgevoerd
Ook Partial Views (MSDN) zijn bedoeld om onderhoudbaarheid van de HTML in een View te verbeteren:
Je neemt een deel van je View op in een Partial View
Dit is een onderdeel van je pagina dat ook op andere pagina’s kan worden hergebruikt
Op deze manier kun je een grotere View in kleinere stukken opdelen
De Partial View is vooral handig voor HTML die in meerdere Views voorkomt maar niet in alle Views:
Deze HTML-code kun je dus niet in een Shared Layout plaatsen
Als je bijvoorbeeld een inlogformulier wilt plaatsen op meerdere pagina’s, dan doe je dat als volgt:
<partial name="_LoginPartial" />
Razor zoekt nu naar "_LoginPartial.cshtml" in Views/Shared.
Je kunt ook data meegeven aan de Partial View
Het is sterk aan te bevelen om een Model mee te geven (zo'n Partial View wordt een Strongly Typed Partial View genoemd).
In de View wordt dan compiletime al gecheckt of de Properties van het Model bestaan
Je kunt dit bijvoorbeeld gebruiken voor een lijst met nieuwsberichten (bijv.: NOS.nl)
@foreach (var artikel in @Model) { // @Model is een List<Artikel>
<partial name="_Artikel" model="artikel" />
} // alternatief: <partial name="_Artikel" for="@artikel" />
Naast een (strongly typed) Model kun je ook gebruik maken van ViewData
Ook hiervoor geldt natuurlijk dat de Action het Model of de ViewData moet vullen
Want: een Partial View heeft geen eigen controller!
Een Model komt door de ORM in de database terecht. Een ViewModel niet.
Een Model hoeft geen View te hebben. Een ViewModel wel.
Een ViewModel heeft geen bestaansrecht zonder de bijbehorende View.
Een Model representeert een entiteit in het domeinmodel, dus staat centraal in de webapplicatie. Een ViewModel allebei niet.
Een Model representeert de toestand van de applicatie met bijbehorende business logica, en kan op veel plekken in de webapplicatie worden gebruikt.
Een ViewModel wordt alleen gebruikt om data mee te geven van een Controller naar View (en vanaf daar mogelijk weer verder naar een Partial View).
ViewModels staan in een andere map dan de Models: voorkomt verwarring
Dit is een architectuur pattern: Model-View-ViewModel (MVVM).
Voorbeeld 1: Company en Project zijn beide een Model.
public class ProjectDashboardViewModel
{
public Company Company { get; set; }
public IEnumerable<Project> AllProjects { get; set; }
public int NumberOfFinishedProjects { get; set; }
public IEnumerable<Project> OverdueProjects { get; set; }
}
Voorbeeld 2: Student is een Model zonder FullName, met CreatedAt.
public class StudentViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName { get; }
}
In mijn View wil ik echter gegevens van het bedrijf laten zien en 2 lijsten; één met alle projecten en één met projecten die achterlopen
Ik kan echter maar 1 Model meegeven aan een view
Je kunt dat natuurlijk omzeilen met ViewData[“OverdueProjects”] = … etc.
Maar een ViewModel biedt een strongly typed oplossing die beter is
public class Movie
{
public int Id { get; set; }
[StringLength(60, MinimumLength = 3)]
[Required]
public string Title { get; set; }
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[Range(1, 100)]
[DataType(DataType.Currency)]
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
[RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]
[Required]
[StringLength(30)]
public string Genre { get; set; }
[RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$")]
[StringLength(5)]
[Required]
public string Rating { get; set; }
}
// GET: Movies/Create
public IActionResult Create()
{
return View();
}
// POST: Movies/Create
[HttpPost]
public void IActionResult Create(Movie movie)
{
if (ModelState.IsValid)
{
_context.Add(movie);
_context.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
Niet alle data-annotaties zijn constraints in de database!
Markeer de IsValid
Bijv. [Regex(...)] en [StringLength(10)] zijn er alleen voor validatie
Bijv. het type van de variabele, [Key] en [MaxLength(10)] bepalen het database schema [en validatie]
(((je kunt ook check constraints toevoegen: modelBuilder.Entity<Student>(s=>s.HasCheckConstraint("constr1", "[Leeftijd] >= 18");)))
public class Student
{
public int Id { get; set; }
[StringLength(10)]
public string Name { get; set; }
[Required]
[Display(Name = "Geboortedatum")]
public DateTime Geboren { get; set; }
[Range(0, 10)]
public double GemiddeldeCijfer { get; set; }
}
(zie les 10b)
<form action="/Students/Create" method="post">
<label for="Name">Name</label>
<input type="text" id="Name" name="Name" value="">
<label for="Geboren">Geboortedatum</label>
<input type="datetime-local" id="Geboren" name="Geboren" value="">
<label for="GemiddeldeCijfer">GemiddeldeCijfer</label>
<input type="text" id="GemiddeldeCijfer" name="GemiddeldeCijfer" value="">
<input type="submit" value="Create">
</form>
Importeer jQuery Unobtrusive Validation (standaard in ASP.NET Core MVC applicatie)
<form action="/Students/Create" method="post">
<label for="Name">Name</label>
<input type="text" data-val="true"
data-val-length="The field Name must be a string with a maximum length of 10."
data-val-length-max="10"
id="Name" maxlength="10" name="Name" value="">
<span data-valmsg-for="Name"></span>
<label for="Geboren">Geboortedatum</label>
<input type="datetime-local" data-val="true"
data-val-required="The Geboortedatum field is required."
id="Geboren" name="Geboren" value="">
<span data-valmsg-for="Geboren"></span>
<label for="GemiddeldeCijfer">GemiddeldeCijfer</label>
<input class="form-control valid" type="text" data-val="true"
data-val-number="The field GemiddeldeCijfer must be a number."
data-val-range="The field GemiddeldeCijfer must be between 0 and 10." data-val-range-max="10" data-val-range-min="0"
data-val-required="The GemiddeldeCijfer field is required."
id="GemiddeldeCijfer" name="GemiddeldeCijfer" value="" aria-describedby="GemiddeldeCijfer-error" aria-invalid="false">
<span data-valmsg-for="GemiddeldeCijfer"></span>
<input type="submit" value="Create">
</form>
<form asp-action="Create">
<div asp-validation-summary="ModelOnly"></div>
<label asp-for="Name"></label>
<input asp-for="Name" />
<span asp-validation-for="Name"></span>
<label asp-for="Geboren"></label>
<input asp-for="Geboren"/>
<span asp-validation-for="Geboren"></span>
<label asp-for="GemiddeldeCijfer"></label>
<input asp-for="GemiddeldeCijfer"/>
<span asp-validation-for="GemiddeldeCijfer"></span>
<input type="submit" value="Create"/>
</form>
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Geboren" class="control-label"></label>
<input asp-for="Geboren" class="form-control" />
<span asp-validation-for="Geboren" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="GemiddeldeCijfer" class="control-label"></label>
<input asp-for="GemiddeldeCijfer" class="form-control" />
<span asp-validation-for="GemiddeldeCijfer" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
Maak een model klasse Student met een Id, 6 properties en een extra property "List<Student> Vrienden".
Voor elk van de 6 properties: voeg een data-attribute toe waarop gevalideerd kan worden client-side.
Scaffold de controller en views voor de CRUD operaties. Sla de studenten op in een SQLite database (of eventueel SQL Server).
Zorg dat er client-side en server-side validatie is.
In de footer van alle webpagina’s wil ik 3 studenten zien die voldoen aan een zelf gekozen criterium (bijv. studenten met het hoogste cijfer). Maak in je Shared View gebruik van een Partial View. Aan de Partial View geef je een IEnumerable van maximaal 3 items mee. Hint: gebruik de LINQ method Take (3).
Zorg dat je van alle gescaffolde code begrijpt wat het betekent.
Bestudeer de gegeneerde HTML van het formulier met Tag Helpers.
ViewData is een Dictionary Object (die je kunt lezen en schrijven)
ViewBag is een Wrapper die verwijst naar ViewData (NB ViewBag zelf kun je dus geen waarde geven)
Het is een heel bijzonder object dat dynamisch eigen properties aanmaakt
Omdat ViewBag een wrapper is om ViewData heen, verwijzen ze naar dezelfde content:
ViewData["Message"] = "text"; vs. ViewBag.Message = "text";
Weet je nog? Als je objecten in ViewData stopt, moet je casten om de waarde te kunnen lezen
Dat is bij ViewBag niet meer nodig (ViewBag organiseert dit zelf)