Le web est basé sur un modèle client/serveur :
Structure HTML contenant les données générée côté serveur, puis renvoyée au client.
Squelette HTML (sans données) initialement renvoyé au client. Ensuite, DOM mis à jour dynamiquement grâce à des appels asynchrones du client vers le serveur.
Avantages :
Inconvénients :
dotnet new mvc -o {AppName}
cd {AppName}
dotnet new gitignore
Controllers/
.{AppName}/Controllers
.Controller
(vues HTML) ou ControllerBase
(API web).namespace MvcMovie.Controllers;
public class HomeController : Controller
{
// ...
public IActionResult Index()
{
return View();
}
public IActionResult Privacy()
{
return View();
}
// ...
}
Models/
.{AppName}/Models
.Views/{ControllerName}
sous la forme de fichiers Razor (.cshtml
).@{
ViewData["Title"] = "Home Page";
}
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>
wwwroot/
.Centralise les paramètres de configuration de l’application.
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore.Database.Command": "Information"
}
}
}
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
var app = builder.Build();
// ...
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
dotnet run
ou depuis VS Code, après avoir généré les assets./{ControllerName}/{ActionName}?{Parameters}
.https://myapp/Student/Details?code=137
appelle la méthode Details
du contrôleur StudentController
, en lui passant un paramètre nommé code
ayant la valeur 137.app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Home
, Index
et id
sont resp. les noms par défaut du contrôleur, de l’action et du paramètre (optionnel).
Dans le fichier Controllers/HelloController.cs
.
using Microsoft.AspNetCore.Mvc;
namespace MvcMovie.Controllers;
public class HelloController : ControllerBase
{
// GET: /Hello/
public string Index()
{
return "Hello World!";
}
}
Index
de la classe HelloController
.Index
étant le nom de l’action par défaut.?{name1}={value1}&{name2}=...
using System.Text.Encodings.Web;
// ...
public class HelloController : ControllerBase
{
// ...
// GET: /Hello/Welcome/
public string Welcome(string name, int numTimes = 1)
{
// Prevents injection attacks
return HtmlEncoder.Default.Encode($"Hello {name}, NumTimes is: {numTimes}");
}
}
Welcome
de la classe HelloController
.name
et numTimes
.ASPNETCORE_ENVIRONMENT
. En l’absence de cette variable, l’environnement est de type Production.Dans le fichier ./vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Launch (web)",
# ...
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
# ...
}
]
}
Dans le fichier Program.cs
.
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
else
{
app.UseDeveloperExceptionPage();
}
Dans le fichier Models/Movie.cs
.
using System.ComponentModel.DataAnnotations;
namespace MvcMovie.Models;
public class Movie
{
public int Id { get; set; }
[StringLength(100, MinimumLength = 3)]
public string Title { get; set; } = null!;
[Display(Name = "Release Date"), DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[StringLength(30)]
public string Genre { get; set; } = null!;
}
Dans le fichier Data/MvcMovieContext.cs
.
using Microsoft.EntityFrameworkCore;
using MvcMovie.Models;
namespace MvcMovie.Data;
public class MvcMovieContext : DbContext
{
public DbSet<Movie> Movies { get; set; } = null!;
public string DbPath { get; private set; }
public MvcMovieContext()
{
// Path to SQLite database file
DbPath = "MvcMovie.db";
}
// The following configures EF to create a SQLite database file locally
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
// Use SQLite as database
options.UseSqlite($"Data Source={DbPath}");
// Optional: log SQL queries to console
options.LogTo(Console.WriteLine, new[] { DbLoggerCategory.Database.Command.Name }, LogLevel.Information);
}
}
Réalisée via des migrations.
# Create a new migration
dotnet ef migrations add {MigratioName}
# Sync DB with most recent migrations
dotnet ef database update
Dans le fichier Program.cs
.
// ...
using MvcMovie.Data;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
// Attach an EF Core database context to each query
builder.Services.AddDbContext<MvcMovieContext>();
// ...
Edition du fichier .gitignore
pour ignorer ces fichiers.
# Ignore SQLite temp files
*.db-shm
*.db-wal
# ...
Classe SeedData
créée dans le répertoire Models/
.
using MvcMovie.Data;
namespace MvcMovie.Models;
public class SeedData
{
public static void Init()
{
using (var context = new MvcMovieContext())
{
// Look for existing content
if (context.Movies.Any())
{
return; // DB already filled
}
// Add several movies
context.Movies.AddRange(
new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-2-12"),
Genre = "Romantic Comedy",
},
// ...
new Movie
{
Title = "Rio Bravo",
ReleaseDate = DateTime.Parse("1959-4-15"),
Genre = "Western"
}
);
// Commit changes into DB
context.SaveChanges();
}
}
}
Dans le fichier Program.cs
.
using MvcMovie.Data;
using MvcMovie.Models;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
// Attach an EF Core database context to each query
builder.Services.AddDbContext<MvcMovieContext>();
var app = builder.Build();
// Seed data into DB
SeedData.Init();
// ...
{
"cars": [
{
"model": "Peugeot",
"color": "blue",
"checkups": [2015, 2017]
},
{
"model": "Citroën",
"color": "white",
"checkups": [2003, 2005, 2007, 2009, 2011, 2013]
}
]
}
GET /<ResourceName>/<id>
pour accéder à une ressource.POST /<ResourceName>
(avec les informations nécessaires dans le corps de la requête) pour la créer.Controllers/
.ControllerBase
.api/{ApiControllerName}/
.Controllers/MovieApiController.cs
.using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MvcMovie.Data;
using MvcMovie.Models;
namespace MvcMovie.Controllers;
[Route("api/[controller]")]
[ApiController]
public class MovieApiController : ControllerBase
{
private readonly MvcMovieContext _context;
public MovieApiController(MvcMovieContext context)
{
_context = context;
}
// GET: api/MovieApi
public async Task<ActionResult<IEnumerable<Movie>>> GetMovies()
{
return await _context.Movies.ToListAsync();
}
}
// ...
[Route("api/[controller]")]
[ApiController]
public class MovieApiController : ControllerBase
{
// ...
// GET: api/MovieApi/5
[HttpGet("{id}")]
public async Task<ActionResult<Movie>> GetMovie(int id)
{
var movie = await _context.Movies.FindAsync(id);
if (movie == null)
return NotFound();
return movie;
}
}
Envoyer une requête HTTP GET vers l’URL https://localhost:{port}/api/movieapi/{movieId}.
Program.cs
) pour ignorer ou préserver les références circulaires.using System.Text.Json.Serialization;
// ...
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Ignore circular references when serializing objects into JSON
builder.Services.AddControllersWithViews().AddJsonOptions(x =>
x.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles);
// ...
// ...
[Route("api/[controller]")]
[ApiController]
public class MovieApiController : ControllerBase
{
// ...
// POST: api/MovieApi
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
[HttpPost]
public async Task<ActionResult<Movie>> PostMovie(Movie movie)
{
_context.Movies.Add(movie);
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(GetMovie), new { id = movie.Id }, movie);
}
}
Content-type
à application/json
.curl -X POST -k -H 'Content-Type: application/json' -i https://localhost:{port}/api/movieapi/ --data '{
"Title": "1941",
"ReleaseDate": "1979-12-14T00:00:00",
"Genre": "Comedy"
}'
// ...
[Route("api/[controller]")]
[ApiController]
public class MovieApiController : ControllerBase
{
// ...
// PUT: api/MovieApi/5
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
[HttpPut("{id}")]
public async Task<IActionResult> PutMovie(int id, Movie movie)
{
if (id != movie.Id)
return BadRequest();
_context.Entry(movie).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(id))
return NotFound();
else
throw;
}
return NoContent();
}
// Returns true if a movie with specified id already exists
private bool MovieExists(int id)
{
return _context.Movies.Any(m => m.Id == id);
}
}
Content-type
à application/json
.curl -X PUT -k -H 'Content-Type: application/json' -i https://localhost:7294/api/movieapi/5 --data '{
"Id": 5,
"Title": "1941 (movie)",
"ReleaseDate": "1979-12-14T00:00:00",
"Genre": "Comedy"
}'
// ...
[Route("api/[controller]")]
[ApiController]
public class MovieApiController : ControllerBase
{
// ...
// DELETE: api/MovieApi/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteMovie(int id)
{
var movie = await _context.Movies.FindAsync(id);
if (movie == null)
return NotFound();
_context.Movies.Remove(movie);
await _context.SaveChangesAsync();
return NoContent();
}
// ...
Envoyer une requête HTTP DELETE vers l’URL https://localhost:{port}/api/movieapi/{movieId}.
Route | Description |
---|---|
GET /api/MovieApi | Renvoie tous les films |
GET /api/MovieApi/{id} | Renvoie un film |
POST /api/MovieApi | Ajoute un nouvel film |
PUT /api/MovieApi/{id} | Met à jour un film |
DELETE /api/MovieApi/{id} | Supprime un film |
{ActionName}
d’un contrôleur Controllers/{CtrlName}Controller
, l’appel de View()
déclenche le rendu de la vue Views/{CtrlName}/{ActionName}.cshtml
.Controller
et non ControllerBase
.@
permet de basculer du HTML au C# dans la page. Il peut être suivi :@maVariable
@(nb1 + nb2)
@{...}
.Views/Shared/_Layout.cshtml
.@RenderBody()
permet de générer le contenu spécifique de la vue à afficher.<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>@ViewData["Title"] - Movie App</title>
<!-- ... -->
</head>
<body>
<header><!-- ... --></header>
<div class="container">
<main role="main" class="pb-3">@RenderBody()</main>
</div>
<footer><!-- ... --></footer>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<!-- ... -->
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
Méthode d’action associée à la route /Hello
dans le fichier Controllers/HelloController.cs
.
using Microsoft.AspNetCore.Mvc;
namespace MvcMovie.Controllers;
public class HelloController : Controller // not ControllerBase!
{
//
// GET: /Hello/
public IActionResult Index()
{
// Renders Views/Hello/Index.cshtml
return View();
}
// ...
}
Fichier Views/Hello/Index.cshtml
.
@*
This view demonstrates some possibilities of the Razor syntax.
(This is a server-side comment, by the way).
*@
<h2>Hello ASP.NET Core MVC!</h2>
@{
@* Some info about a famous monument. *@
string monument = "Eiffel Tower";
int year = 1889;
}
<p>The @monument was inaugurated in @year. It is @(DateTime.Now.Year - year) years old!</p>
@{
string[] colors = { "blue", "brown", "black" };
}
@if (colors.Length > 0)
{
<p>A few colors:</p>
<ul>
@foreach (string color in colors)
{
<li>Color: @color</li>
}
</ul>
}
@{...}
.<text>
permet de demander explicitement le rendu HTML d’un contenu C# sans balises.<p>
@{
void RenderStrong(string value)
{
<strong>@value</strong>
}
int count = 5;
while (count > 0)
{
<text>@count... </text>
count--;
}
RenderStrong("BOOM!");
}
</p>
<a>
sont utilisées pour les créer.asp-controller
, asp-action
, asp-route-id
et asp-route-{paramName}
permettent la navigation entre actions.@{
string visitorName = "Garance";
}
<p>
Here's an <a href="https://fr.wikipedia.org/wiki/Tour_Eiffel">external link</a>,
a <a asp-controller="Home" asp-action="Index">link to the home page</a>
and a <a asp-action="Welcome" asp-route-name="@visitorName" asp-route-numtimes="5">link with parameters</a>.
</p>
Approche faiblement typée : on rassemble les paramètres de la vue dans un dictionnaire nommé ViewData.
using Microsoft.AspNetCore.Mvc;
namespace MvcMovie.Controllers;
public class HelloController : Controller
{
// ...
// GET: /Hello/Welcome/
public IActionResult Welcome(string name, int numTimes = 0)
{
ViewData["Message"] = "Hello " + name;
ViewData["NumTimes"] = numTimes;
return View();
}
}
Dans le fichier Views/Hello/Welcome.cshtml
.
@{
ViewData["Title"] = "Welcome";
int numTimes = (int)ViewData["NumTimes"]!;
}
<h2>Welcome!</h2>
<ul>
@for (int i = 0; i < numTimes; i++)
{
<li>@ViewData["Message"]</li>
}
</ul>
Model
;@Html.DisplayNameFor
renvoie le nom d’une propriété du modèle (explication).@Html.DisplayFor
renvoie la valeur d’une propriété du modèle.Fichier Controllers/MovieController.cs
.
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MvcMovie.Data;
namespace MvcMovie.Controllers;
public class MovieController : Controller
{
private readonly MvcMovieContext _context;
public MovieController(MvcMovieContext context)
{
_context = context;
}
// GET: Movie
public async Task<IActionResult> Index()
{
var movies = await _context.Movies.OrderBy(m => m.Title).ToListAsync();
return View(movies);
}
}
Fichier Views/Movie/Index.cshtml
.
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewData["Title"] = "Movies";
}
<h2>Movies</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Title)
</th>
<th>
@Html.DisplayNameFor(model => model.ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Genre)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-id="@item.Id">Details</a> |
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Méthode d’action associée à la route /Movie/Details/{Id}
.
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MvcMovie.Data;
namespace MvcMovie.Controllers;
public class MovieController : Controller
{
// ...
// GET: Movies/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movies
.FirstOrDefaultAsync(m => m.Id == id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
}
Fichier Views/Movie/Details.cshtml
.
@model MvcMovie.Models.Movie
@{
ViewData["Title"] = "Details";
}
<h2>@Html.DisplayFor(model => model.Title)</h2>
<div>
<hr />
<dl class="row">
<dt class="col-sm-3">
@Html.DisplayNameFor(model => model.ReleaseDate)
</dt>
<dd class="col-sm-9">
@Html.DisplayFor(model => model.ReleaseDate)
</dd>
<dt class="col-sm-3">
@Html.DisplayNameFor(model => model.Genre)
</dt>
<dd class="col-sm-9">
@Html.DisplayFor(model => model.Genre)
</dd>
</dl>
</div>
<div>
<a asp-action="Edit" asp-route-id="@Model.Id">Edit</a> |
<a asp-action="Index">Back to List</a>
</div>
Fichier Models/Movie.cs
.
using System.ComponentModel.DataAnnotations;
namespace MvcMovie.Models;
public class Movie
{
public int Id { get; set; }
[StringLength(100, MinimumLength = 3)]
public string Title { get; set; } = null!;
[Display(Name = "Release Date"), DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[StringLength(30)]
public string Genre { get; set; } = null!;
}
Méthode d’action associée à une requête HTTP GET vers la route /Movie/Create
.
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MvcMovie.Data;
namespace MvcMovie.Controllers;
public class MovieController : Controller
{
// ...
// GET: Movie/Create
public IActionResult Create()
{
return View();
}
}
Fichier Views/Movie/Create.cshtml
.
@model MvcMovie.Models.Movie
@{
ViewData["Title"] = "Create";
}
<h2>Create a Movie</h2>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Title" class="control-label"></label>
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ReleaseDate" class="control-label"></label>
<input asp-for="ReleaseDate" class="form-control" />
<span asp-validation-for="ReleaseDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Genre" class="control-label"></label>
<input asp-for="Genre" class="form-control" />
<span asp-validation-for="Genre" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Méthode d’action associée à une requête HTTP POST vers la route /Movie/Create
.
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MvcMovie.Data;
using MvcMovie.Models;
namespace MvcMovie.Controllers;
public class MovieController : Controller
{
// ...
// POST: Movie/Create
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,Title,ReleaseDate,Genre")] Movie movie)
{
// Apply model validation rules
if (ModelState.IsValid)
{
_context.Add(movie);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
// At this point, something failed: redisplay form
return View(movie);
}
}
Méthode d’action associée à une requête HTTP GET vers la route /Movie/Edit/{Id}
.
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MvcMovie.Data;
using MvcMovie.Models;
namespace MvcMovie.Controllers;
public class MovieController : Controller
{
// ...
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movies.FindAsync(id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
}
Fichier Views/Movie/Edit.cshtml
.
@model MvcMovie.Models.Movie
@{
ViewData["Title"] = "Edit";
}
<h2>Edit a Movie</h2>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Id" />
<div class="form-group">
<label asp-for="Title" class="control-label"></label>
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ReleaseDate" class="control-label"></label>
<input asp-for="ReleaseDate" class="form-control" />
<span asp-validation-for="ReleaseDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Genre" class="control-label"></label>
<input asp-for="Genre" class="form-control" />
<span asp-validation-for="Genre" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Méthode d’action associée à une requête HTTP POST vers la route /Movie/Edit
.
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MvcMovie.Data;
using MvcMovie.Models;
namespace MvcMovie.Controllers;
public class MovieController : Controller
{
// ...
// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
private bool MovieExists(int id)
{
return _context.Movies.Any(e => e.Id == id);
}
}
Méthode d’action associée à la route Movie/Delete/{Id}
.
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MvcMovie.Data;
using MvcMovie.Models;
namespace MvcMovie.Controllers;
public class MovieController : Controller
{
// ...
// GET: Movies/Delete/5
public async Task<IActionResult> Delete(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movies
.FirstOrDefaultAsync(m => m.Id == id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
}
@model MvcMovie.Models.Movie
@{
ViewData["Title"] = "Delete";
}
<h2>Delete a Movie</h2>
<h4>Are you sure?</h4>
<div>
<hr />
<dl class="row">
<dt class="col-sm-3">
@Html.DisplayNameFor(model => model.Title)
</dt>
<dd class="col-sm-9">
@Html.DisplayFor(model => model.Title)
</dd>
<dt class="col-sm-3">
@Html.DisplayNameFor(model => model.ReleaseDate)
</dt>
<dd class="col-sm-9">
@Html.DisplayFor(model => model.ReleaseDate)
</dd>
<dt class="col-sm-3">
@Html.DisplayNameFor(model => model.Genre)
</dt>
<dd class="col-sm-9">
@Html.DisplayFor(model => model.Genre)
</dd>
</dd>
</dl>
<form asp-action="Delete">
<input type="hidden" asp-for="Id" />
<input type="submit" value="Delete" class="btn btn-danger" /> |
<a asp-action="Index">Back to List</a>
</form>
</div>
Méthode d’action associée à une requête HTTP POST vers la route Movie/Delete
.
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MvcMovie.Data;
using MvcMovie.Models;
namespace MvcMovie.Controllers;
public class MovieController : Controller
{
// ...
// POST: Movies/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
var movie = await _context.Movies.FindAsync(id);
_context.Movies.Remove(movie!);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
}