19-10 Modification d'une question
Pour modifier une question, on utilise la même recette que pour la création. C'est-à-dire qu'on aura un ViewModel
pour la mise à jour, une action de contrôleur, ainsi qu'une vue avec un formulaire.
Ajout de l'action QuestionsController#Edit
Commençons par ajouter une version simple de l'action Edit
, qui est une action GET
puisque son rôle est d'afficher le formulaire de modification.
[HttpGet("{id:int}/edit")] //GET /evenements/{evenementId}/questions/{id}/edit
[Authorize]
public async Task<IActionResult> Edit(int id, int evenementId)
{
return Ok();
}
[HttpGet("{id:int}/edit")]
actionGET
vers la route/evenements/{evenementId}/questions/{id}/edit
[Authorize]
on doit être identifié pour accéder à cette route- On reçoit comme paramètre
id
le id de la questionevenementId
le id de l'événement qui provient de la route principale associée au contrôleur
Ajout de l'action Questions#Update
Ensuite, une action Update
sera requise pour recevoir les données du formulaire et sauvegarder dans la base de données.
Tout comme pour Edit
, on commence avec une version de base.
// PATCH /evenements/{evenementId}/questions/{id}
[HttpPatch("{id:int}")]
[Authorize]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Update(int id, int evenementId, ModifierQuestionViewModel modifierQuestionViewModel)
{
return Ok();
}
La différence ici par rapport à Edit est que:
- La méthode HTTP visée par l'action est
PATCH
- On accepte un paramètre de plus, soit le
ViewModel
de modification de question.
Ajout d'un lien pour modifier une question
On veut pouvoir accéder à cette vue d'édition de question!
Dans la vue Index
, ajoutez un lien Modifier
pour chaque question vers l'action Edit
du contrôleur de questions.
<!-- ... -->
<div class="row">
<div class="col-12 col-lg-6">
@foreach (Question question in Model.Questions)
{
<div class="card mb-3">
<div class="card-body">
<p class="card-text" style="white-space: pre-line">@question.Contenu</p>
<a class="card-link small"
asp-controller="Questions"
asp-action="Edit"
asp-route-evenementId="@Model.Evenement.Id"
asp-route-id="@question.Id">Modifier</a>
</div>
</div>
}
</div>
</div>
Vous devriez maintenant voir un lien Modifier
à côté des questions de l'utilisateur, pour un événement donné.

Évidemment, pour l'instant cela mène vers une page vide.
Récupérer la question à modifier
Pour modifier une question, il faut être en mesure de la récupérer! Vous l'aurez deviné, on devra ajouter une fonction FindById
à QuestionService
et QuestionRepository
!
Ajout de FindById()
à QuestionService
et QuestionRepository
IQuestionRepository
Snowfall.Data/Repositories/IQuestionRepository.cspublic interface IQuestionRepository
{
Task<Question?> FindById(int id);
Task<List<Question>> FindByEvenementIdAndUserId(int evenementId, string userId);
Task<Question> Create(Question question);
}IQuestionService
Snowfall.Application/Services/IQuestionService.cspublic interface IQuestionService
{
Task<Question?> FindById(int id);
Task<List<Question>> FindByEvenementIdAndUserId(int evenementId, string userId);
Task<Question> Create(Question question);
}QuestionRepository
. Utilisez l'aide contextuelle en cliquant sur le nom de la classe et utilisezImplement missing members
. Implémentez la fonction pour obtenir une question en fonction d'unid
.Snowfall.Data/Repositories/QuestionRepository.cspublic async Task<Question?> FindById(int id)
{
string sql = @"
SELECT * from questions
WHERE id = @Id
";
using (IDbConnection connection = _dbContext.CreateConnection())
{
var param = new
{
Id = id
};
Question question = await connection.QuerySingleOrDefaultAsync<Question>(sql, param);
return question;
}
}QuestionService
. Idem pour le service, utilisez l'aide contextuelle en cliquant sur le nom de la classe et utilisezImplement missing members
.Snowfall.Application/Services/QuestionService.cspublic async Task<Question?> FindById(int id)
{
return await _questionRepository.FindById(id);
}
Récupérer la question demandée dans l'action Edit
On peut maintenant utiliser cette nouvelle fonction de service/repository pour récupérer la question à modifier dans la vue Edit
.
- Récupérez l'événement
public async Task<IActionResult> Edit(int id, int evenementId)
{
var evenement = await _evenementService.FindById(evenementId);
return Ok();
} - Récupérez la question
public async Task<IActionResult> Edit(int id, int evenementId)
{
var evenement = await _evenementService.FindById(evenementId);
var question = await _questionService.FindById(id);
return Ok();
} - Faire les vérifications d'usage si les
id
de l'événement ou de la question n'existe pas.public async Task<IActionResult> Edit(int id, int evenementId)
{
var evenement = await _evenementService.FindById(evenementId);
var question = await _questionService.FindById(id);
if (evenement == null || question == null)
return NotFound();
return Ok();
}
Ajout d'une vue Edit
Création de la vue de base
- Sous
Views/Questions
, ajoutez une vueEdit
(Razor MVC View with Layout) - Associez la vue au
ViewModel
ModifierQuestionViewModel
et changez le titreSnowfall.Web.Mvc/Views/Questions/Edit.cshtml@model Snowfall.Web.Mvc.Models.Questions.ModifierQuestionViewModel
@{
ViewBag.Title = "Modifier la question";
}
<h1>@ViewBag.Title</h1>
Ajout du form
dans la vue Edit
Vous pouvez réutiliser sensiblement la même chose que dans New
, à l'exception de asp-action
et asp-route-id
:
<!-- ... -->
<div class="row">
<div class="col-md-6 offset-md-3">
<h1>@ViewBag.Title</h1>
<form asp-controller="Questions"
asp-action="Update"
asp-route-evenementId="@Model.EvenementId"
asp-route-id="@Model.Id">
<input type="hidden" asp-for="EvenementId"/>
<div class="mb-3">
<textarea asp-for="Contenu" class="form-control" rows="5"></textarea>
<span asp-validation-for="Contenu" class="invalid-feedback"></span>
</div>
<button class="btn btn-primary">Soumettre</button>
</form>
</div>
</div>
Remarquez que l'action visée par le formulaire est maintenant Update
plutôt que Create
.
De plus, on doit ajouter un paramètre attendu par l'action Update
, soit le id
de la question à modifier.
Retourner la vue Edit
dans QuestionsController#Edit
Pour afficher la vue, retournons View
plutôt que Ok
avec un ViewModel initialisé avec les données pertinentes à l'édition, soit:
- le id de l'événement
- le id de la question
- le contenu de la question
public async Task<IActionResult> Edit(int id, int evenementId)
{
var evenement = await _evenementService.FindById(evenementId);
var question = await _questionService.FindById(id);
if (evenement == null || question == null)
return NotFound();
var viewModel = new ModifierQuestionViewModel()
{
Id = question.Id,
EvenementId = question.EvenementId,
Contenu = question.Contenu
};
return View(viewModel);
}
Test d'affichage
Lancez le site dans le navigateur et cliquez sur un lien de modification d'une question. En temps normal, vous obtiendrez ceci:

Par contre, faire soumettre mène à une erreur 405
qui dit essentiellement que la méthode HTTP n'est pas supportée par le navigateur :(
Si vous regardez dans l'onglet Réseau des outils de développeurs du navigateur, vous remarquerez que le formulaire fait un POST
alors que le serveur dit qu'il accepte PATCH
pour cette URL!
Ajout du input caché _method
C'est que nous n'avons pas dit au formulaire d'utiliser une autre méthode que POST
pour cette action! En fait, on n'a pas dit au serveur de remplacer le POST
par un PATCH
.
C'est ce que nous avons configuré au niveau précédent, le support pour d'autres actions HTTP via un middleware.
Dans le formulaire, il ne suffit que d'ajouter ceci:
<!-- ... -->
<form asp-controller="Questions"
asp-action="Update"
asp-route-evenementId="@Model.EvenementId"
asp-route-id="@Model.Id">
<input type="hidden" name="_method" value="PATCH"/>
<!-- ... -->
Maintenant, si vous réessayez, il n'y aura plus d'erreur! Une page vide puisqu'on ne retourne que Ok()
, mais au moins on se rend jusqu'à Update! :)
Encore QuestionService
et QuestionRepository
!
Il faut procéder à la mise à jour maintenant et vous l'aurez deviné... une fonction de repo et service sera requise!
On doit en effet ajouter Update()
à QuestionService
et QuestionRepository
.
IQuestionRepository
Snowfall.Data/Repositories/IQuestionRepository.cspublic interface IQuestionRepository
{
Task<Question?> FindById(int id);
Task<List<Question>> FindByEvenementIdAndUserId(int evenementId, string userId);
Task<Question> Create(Question question);
Task<bool> Update(Question question);
}IQuestionService
Snowfall.Application/Services/IQuestionService.cspublic interface IQuestionService
{
Task<Question?> FindById(int id);
Task<List<Question>> FindByEvenementIdAndUserId(int evenementId, string userId);
Task<Question> Create(Question question);
Task<bool> Update(Question question);
}QuestionRepository
Utilisez l'aide contextuelle en cliquant sur le nom de la classe et utilisezImplement missing members
. Implémentez la fonction pour modifier une question en fonction d'unid
.Snowfall.Data/Repositories/QuestionRepository.cspublic async Task<bool> Update(Question question)
{
string sql = @"
UPDATE questions SET
utilisateur_id = @UtilisateurId,
evenement_id = @EvenementId,
contenu = @Contenu,
updated_at = @UpdatedAt
WHERE id = @Id";
using (IDbConnection connection = _dbContext.CreateConnection())
{
question.UpdatedAt = DateTime.Now;
var affectedRows = await connection.ExecuteAsync(sql, question);
return affectedRows == 1;
}
}QuestionService
Idem pour le service, utilisez l'aide contextuelle en cliquant sur le nom de la classe et utilisezImplement missing members
.Snowfall.Application/Services/QuestionService.cspublic async Task<bool> Update(Question question)
{
return await _questionRepository.Update(question);
}
Sauvegarder les modifications (FINALEMENT! 🤯)
// PATCH /evenements/{evenementId}/questions/{id}
[HttpPatch("{id:int}")]
[Authorize]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Update(int id, int evenementId, ModifierQuestionViewModel modifierQuestionViewModel)
{
var evenement = await _evenementService.FindById(evenementId);
var question = await _questionService.FindById(id);
if (evenement == null || question == null)
return NotFound();
if (!ModelState.IsValid)
{
return View("Edit", modifierQuestionViewModel);
}
question.Contenu = modifierQuestionViewModel.Contenu;
await _questionService.Update(question);
return RedirectToAction("Index", "Questions", new { evenementId = evenementId });
}
Essayez et ça devrait fonctionner! 🎉