Kurs:Täglich Controllers Übungen und Kurs:Täglich Model Binding Übungen: Unterschied zwischen den Seiten

Aus ahrensburg.city
(Unterschied zwischen Seiten)
Zur Navigation springen Zur Suche springen
Die Seite wurde neu angelegt: „= ASP.NET Core MVC: Controller & Action Methods – Code-Spickzettel = == 1. Einfacher Controller mit Action Methods == <syntaxhighlight lang="csharp"> // HomeController.cs namespace MyApp.Controllers { [Controller] public class HomeController : Controller { [Route("home")] [Route("/")] public string Index() { return "Hello from Index"; } [Route("about")] public string About…“
 
Die Seite wurde neu angelegt: „= Model Binding & Validation – ASP.NET Core MVC (Spickzettel) = == Standard-Model-Binding (Query, Route, Form) == <syntaxhighlight lang="csharp"> // Route mit optionalen Parametern (aus Query oder Route gebunden) [Route("buchladen/{buchid?}/{eingeloggt?}")] public IActionResult Index(int? buchid, bool? eingeloggt) { return Content($"Buch-ID: {buchid}, Eingeloggt: {eingeloggt}", "text/plain"); } // Route mit festem (required) Parameter (aus Route…“
 
Zeile 1: Zeile 1:
= ASP.NET Core MVC: Controller & Action Methods – Code-Spickzettel =
= Model Binding & Validation – ASP.NET Core MVC (Spickzettel) =
 
== Standard-Model-Binding (Query, Route, Form) ==


== 1. Einfacher Controller mit Action Methods ==
<syntaxhighlight lang="csharp">
<syntaxhighlight lang="csharp">
// HomeController.cs
// Route mit optionalen Parametern (aus Query oder Route gebunden)
namespace MyApp.Controllers
[Route("buchladen/{buchid?}/{eingeloggt?}")]
public IActionResult Index(int? buchid, bool? eingeloggt)
{
    return Content($"Buch-ID: {buchid}, Eingeloggt: {eingeloggt}", "text/plain");
}
 
// Route mit festem (required) Parameter (aus Route gebunden)
[Route("shop/buecher/{id}")]
public IActionResult Buch()
{
{
     [Controller]
     int id = Convert.ToInt32(Request.RouteValues["id"]);
     public class HomeController : Controller
     return Content($"<h1>Buch-Shop {id}</h1>", "text/html");
    {
}
        [Route("home")]
</syntaxhighlight>
        [Route("/")]
        public string Index()
        {
            return "Hello from Index";
        }


        [Route("about")]
== [FromQuery] und [FromRoute] verwenden ==
        public string About()
        {
            return "Hello from About";
        }


        [Route("contact-us/{mobile:regex(^\\d{10}$)}")]
<syntaxhighlight lang="csharp">
        public string Contact(string mobile)
[Route("buchladen/{buchid?}/{eingeloggt?}")]
        {
public IActionResult Index([FromQuery] int? buchid, [FromRoute] bool? eingeloggt)
            return $"Contact: {mobile}";
{
        }
    // Explizites Binding: buchid nur aus Query, eingeloggt nur aus Route
    }
    return Content($"Buch-ID: {buchid}, Eingeloggt: {eingeloggt}");
}
}
</syntaxhighlight>
</syntaxhighlight>


== 2. ContentResult: Rohdaten zurückgeben ==
== Model Binding mit Modelklassen ==
 
<syntaxhighlight lang="csharp">
<syntaxhighlight lang="csharp">
[Route("rawtext")]
// Modelklasse
public ContentResult RawText()
public class Buch
{
{
     return Content("Nur Text!", "text/plain");
     public int? BuchId { get; set; }
    public string? Autor { get; set; }
    public override string ToString() => $"Buch: {BuchId}, Autor: {Autor}";
}
}


[Route("html")]
// Controller
public ContentResult HtmlExample()
[Route("buchladen/{buchid?}/{eingeloggt?}")]
public IActionResult Index([FromQuery] int? buchid, [FromRoute] bool? eingeloggt, Buch buch)
{
{
     return Content("<h2>Hallo als HTML</h2>", "text/html");
     return Content($"Buch-ID: {buchid}, Buch: {buch}", "text/plain");
}
}
</syntaxhighlight>
</syntaxhighlight>


== 3. JsonResult: JSON zurückgeben ==
== Model Validation mit DataAnnotations ==
 
<syntaxhighlight lang="csharp">
<syntaxhighlight lang="csharp">
[Route("person")]
using System.ComponentModel.DataAnnotations;
public JsonResult Person()
 
public class Person
{
{
     var person = new { Id = 1, Name = "Anna", Age = 23 };
     [Required(ErrorMessage = "{0} darf nicht leer sein")]
     return Json(person); // Content-Type: application/json
    public string? Name { get; set; }
 
    [EmailAddress]
    public string? Email { get; set; }
 
    [Compare("Passwort", ErrorMessage = "Passwörter stimmen nicht überein")]
    public string? PasswortBestätigung { get; set; }
}
 
// Controller
public IActionResult Erstellen(Person person)
{
     if (!ModelState.IsValid)
        return View(person);
    // Speicherung etc.
}
}
</syntaxhighlight>
</syntaxhighlight>


== 4. File Results: Dateien bereitstellen ==
== Custom Validation Attribute Beispiel ==
 
<syntaxhighlight lang="csharp">
<syntaxhighlight lang="csharp">
// Aus wwwroot (z.B. /wwwroot/sample.pdf)
// Validator: ToDate >= FromDate
[Route("file-download")]
public class DatumsbereichValidatorAttribute : ValidationAttribute
public VirtualFileResult Download1()
{
{
     return File("/sample.pdf", "application/pdf");
     public string VonProperty { get; }
    public DatumsbereichValidatorAttribute(string vonProperty) => VonProperty = vonProperty;
    protected override ValidationResult? IsValid(object? value, ValidationContext context)
    {
        if (value is DateTime toDate)
        {
            var fromProp = context.ObjectType.GetProperty(VonProperty);
            if (fromProp?.GetValue(context.ObjectInstance) is DateTime fromDate && fromDate > toDate)
                return new ValidationResult(ErrorMessage, new[] { VonProperty, context.MemberName! });
        }
        return ValidationResult.Success;
    }
}
}
</syntaxhighlight>


// Von Festplatte (Vorsicht bei Pfaden!)
== Model-Level Validation mit IValidatableObject ==
[Route("file-download2")]
 
public PhysicalFileResult Download2()
<syntaxhighlight lang="csharp">
public class Person : IValidatableObject
{
{
     return PhysicalFile(@"c:\files\sample.pdf", "application/pdf");
     public DateTime? Geburtsdatum { get; set; }
    public int? Alter { get; set; }
 
    public IEnumerable<ValidationResult> Validate(ValidationContext context)
    {
        if (!Geburtsdatum.HasValue && !Alter.HasValue)
            yield return new ValidationResult("Geburtsdatum ODER Alter muss angegeben werden", new[] { nameof(Alter) });
    }
}
}
</syntaxhighlight>
== [Bind] und [BindNever] Attribute ==


// Aus Speicher (byte[])
<syntaxhighlight lang="csharp">
[Route("file-download3")]
// Nur bestimmte Felder binden
public FileContentResult Download3()
[HttpPost]
public IActionResult Erstellen([Bind("Titel", "Beschreibung")] Produkt produkt) { ... }
 
// Niemals aus Request binden
public class Produkt
{
{
     byte[] data = System.IO.File.ReadAllBytes(@"c:\files\sample.pdf");
     [BindNever]
     return File(data, "application/pdf");
     public DateTime ErstelltAm { get; set; }
}
}
</syntaxhighlight>
</syntaxhighlight>


== 5. IActionResult: Verschiedene Rückgaben möglich ==
== [FromBody]: JSON/XML Body-Parameter binden ==
 
<syntaxhighlight lang="csharp">
<syntaxhighlight lang="csharp">
[Route("validate")]
[Route("registrieren")]
public IActionResult Validate(int bookid = 0)
public IActionResult Index([FromBody] Person person)
{
{
     if (bookid <= 0)
     if (!ModelState.IsValid) { ... }
        return BadRequest("Book id ungültig");
     return Content($"{person}");
     if (bookid > 1000)
        return NotFound("Book id zu groß");
    return File("/sample.pdf", "application/pdf");
}
}
</syntaxhighlight>
</syntaxhighlight>


== 6. Status Code Results ==
== Collection Binding (Listen/Arrays binden) ==
 
<syntaxhighlight lang="csharp">
<syntaxhighlight lang="csharp">
// 400 Bad Request
public class Person
return BadRequest("Fehlerhafte Anfrage!");
{
    public List<string?> Interessen { get; set; } = new();
}


// 404 Not Found
// JSON: { "Name": "Anna", "Interessen": ["Musik", "Lesen", "Coding"] }
return NotFound("Nicht gefunden!");
public IActionResult Index(Person person)
{
    return Content($"Person: {person.Name}, Interessen: {string.Join(",", person.Interessen)}");
}
</syntaxhighlight>


// 401 Unauthorized
== [FromHeader]: Parameter aus HTTP-Header ==
return Unauthorized();


// Beliebiger Code
<syntaxhighlight lang="csharp">
return StatusCode(418, "Ich bin eine Teekanne"); // RFC Joke ;-)
[Route("registrieren")]
public IActionResult Index(Person person, [FromHeader(Name = "User-Agent")] string userAgent)
{
    return Content($"{person}, {userAgent}");
}
</syntaxhighlight>
</syntaxhighlight>


== 7. Redirects ==
== Input Formatters registrieren (z.B. XML) ==
 
<syntaxhighlight lang="csharp">
<syntaxhighlight lang="csharp">
// Zu einer URL
var builder = WebApplication.CreateBuilder(args);
return Redirect("/home");
builder.Services.AddControllers().AddXmlSerializerFormatters();
</syntaxhighlight>


// Zu einer Action im gleichen Controller
== Custom Model Binder registrieren ==
return RedirectToAction("About");


// Zu einer Action in anderem Controller
<syntaxhighlight lang="csharp">
return RedirectToAction("Details", "Books", new { id = 42 });
// Custom Binder implementieren
public class PersonModelBinder : IModelBinder { ... }


// Nur lokal (Schutz gegen Open Redirects)
// BinderProvider registrieren (Startup/Program.cs)
return LocalRedirect("/safe-page");
builder.Services.AddControllers(options => {
    options.ModelBinderProviders.Insert(0, new PersonBinderProvider());
});
</syntaxhighlight>


// Permanent
----
return RedirectPermanent("/neue-seite");
</syntaxhighlight>


== 8. Übungen ==
= Übungen / Mini-Aufgaben =
* Schreibe einen Controller namens `ProductController` mit Action Methods:
** `List()` → gibt einen Text "Alle Produkte" zurück
** `Details(int id)` → gibt JSON zurück: { "id": id, "name": "Produktname" }
** `DownloadManual()` → gibt eine Datei aus wwwroot zurück
* Baue Routing mit [Route] Attributen ein.
* Füge eine Action hinzu, die einen 404-Status zurückgibt, falls die id > 100 ist.


== 9. Wichtige Interview-Stichworte ==
* Schreibe eine Controller-Methode, die einen Parameter aus der Query und einen aus der Route bindet.
* Controller: Endet immer auf *Controller*
* Erstelle ein Model mit mindestens drei Properties und validiere sie mit DataAnnotations.
* Action-Methoden: public, beliebiger Name
* Schreibe ein Custom ValidationAttribute, das prüft, ob ein Datum in der Zukunft liegt.
* Rückgabewerte: ContentResult, JsonResult, FileResult, IActionResult, RedirectResult usw.
* Nutze [FromBody], um ein komplettes Model aus JSON im Request-Body zu binden.
* Attribute Routing: [Route], [HttpGet], [HttpPost], ...
* Verwende [BindNever], um eine Eigenschaft vor Overposting zu schützen.
* Statuscodes: 200, 400, 401, 404 etc. per Helper-Methoden
* Binde eine Liste von Strings im Model und fülle sie über ein Array im Request.
* Hole den User-Agent per [FromHeader] und gib ihn in der Response aus.

Aktuelle Version vom 24. Juni 2025, 04:23 Uhr

Model Binding & Validation – ASP.NET Core MVC (Spickzettel)

Standard-Model-Binding (Query, Route, Form)

// Route mit optionalen Parametern (aus Query oder Route gebunden)
[Route("buchladen/{buchid?}/{eingeloggt?}")]
public IActionResult Index(int? buchid, bool? eingeloggt)
{
    return Content($"Buch-ID: {buchid}, Eingeloggt: {eingeloggt}", "text/plain");
}

// Route mit festem (required) Parameter (aus Route gebunden)
[Route("shop/buecher/{id}")]
public IActionResult Buch()
{
    int id = Convert.ToInt32(Request.RouteValues["id"]);
    return Content($"<h1>Buch-Shop {id}</h1>", "text/html");
}

[FromQuery] und [FromRoute] verwenden

[Route("buchladen/{buchid?}/{eingeloggt?}")]
public IActionResult Index([FromQuery] int? buchid, [FromRoute] bool? eingeloggt)
{
    // Explizites Binding: buchid nur aus Query, eingeloggt nur aus Route
    return Content($"Buch-ID: {buchid}, Eingeloggt: {eingeloggt}");
}

Model Binding mit Modelklassen

// Modelklasse
public class Buch
{
    public int? BuchId { get; set; }
    public string? Autor { get; set; }
    public override string ToString() => $"Buch: {BuchId}, Autor: {Autor}";
}

// Controller
[Route("buchladen/{buchid?}/{eingeloggt?}")]
public IActionResult Index([FromQuery] int? buchid, [FromRoute] bool? eingeloggt, Buch buch)
{
    return Content($"Buch-ID: {buchid}, Buch: {buch}", "text/plain");
}

Model Validation mit DataAnnotations

using System.ComponentModel.DataAnnotations;

public class Person
{
    [Required(ErrorMessage = "{0} darf nicht leer sein")]
    public string? Name { get; set; }

    [EmailAddress]
    public string? Email { get; set; }

    [Compare("Passwort", ErrorMessage = "Passwörter stimmen nicht überein")]
    public string? PasswortBestätigung { get; set; }
}

// Controller
public IActionResult Erstellen(Person person)
{
    if (!ModelState.IsValid)
        return View(person);
    // Speicherung etc.
}

Custom Validation Attribute Beispiel

// Validator: ToDate >= FromDate
public class DatumsbereichValidatorAttribute : ValidationAttribute
{
    public string VonProperty { get; }
    public DatumsbereichValidatorAttribute(string vonProperty) => VonProperty = vonProperty;
    protected override ValidationResult? IsValid(object? value, ValidationContext context)
    {
        if (value is DateTime toDate)
        {
            var fromProp = context.ObjectType.GetProperty(VonProperty);
            if (fromProp?.GetValue(context.ObjectInstance) is DateTime fromDate && fromDate > toDate)
                return new ValidationResult(ErrorMessage, new[] { VonProperty, context.MemberName! });
        }
        return ValidationResult.Success;
    }
}

Model-Level Validation mit IValidatableObject

public class Person : IValidatableObject
{
    public DateTime? Geburtsdatum { get; set; }
    public int? Alter { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext context)
    {
        if (!Geburtsdatum.HasValue && !Alter.HasValue)
            yield return new ValidationResult("Geburtsdatum ODER Alter muss angegeben werden", new[] { nameof(Alter) });
    }
}

[Bind] und [BindNever] Attribute

// Nur bestimmte Felder binden
[HttpPost]
public IActionResult Erstellen([Bind("Titel", "Beschreibung")] Produkt produkt) { ... }

// Niemals aus Request binden
public class Produkt
{
    [BindNever]
    public DateTime ErstelltAm { get; set; }
}

[FromBody]: JSON/XML Body-Parameter binden

[Route("registrieren")]
public IActionResult Index([FromBody] Person person)
{
    if (!ModelState.IsValid) { ... }
    return Content($"{person}");
}

Collection Binding (Listen/Arrays binden)

public class Person
{
    public List<string?> Interessen { get; set; } = new();
}

// JSON: { "Name": "Anna", "Interessen": ["Musik", "Lesen", "Coding"] }
public IActionResult Index(Person person)
{
    return Content($"Person: {person.Name}, Interessen: {string.Join(",", person.Interessen)}");
}

[FromHeader]: Parameter aus HTTP-Header

[Route("registrieren")]
public IActionResult Index(Person person, [FromHeader(Name = "User-Agent")] string userAgent)
{
    return Content($"{person}, {userAgent}");
}

Input Formatters registrieren (z.B. XML)

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers().AddXmlSerializerFormatters();

Custom Model Binder registrieren

// Custom Binder implementieren
public class PersonModelBinder : IModelBinder { ... }

// BinderProvider registrieren (Startup/Program.cs)
builder.Services.AddControllers(options => {
    options.ModelBinderProviders.Insert(0, new PersonBinderProvider());
});

Übungen / Mini-Aufgaben

  • Schreibe eine Controller-Methode, die einen Parameter aus der Query und einen aus der Route bindet.
  • Erstelle ein Model mit mindestens drei Properties und validiere sie mit DataAnnotations.
  • Schreibe ein Custom ValidationAttribute, das prüft, ob ein Datum in der Zukunft liegt.
  • Nutze [FromBody], um ein komplettes Model aus JSON im Request-Body zu binden.
  • Verwende [BindNever], um eine Eigenschaft vor Overposting zu schützen.
  • Binde eine Liste von Strings im Model und fülle sie über ein Array im Request.
  • Hole den User-Agent per [FromHeader] und gib ihn in der Response aus.