Aller au contenu principal

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.

Snowfall.Web.Mvc/Controllers/QuestionsController.cs
[HttpGet("{id:int}/edit")] //GET /evenements/{evenementId}/questions/{id}/edit
[Authorize]
public async Task<IActionResult> Edit(int id, int evenementId)
{
return Ok();
}
info
  • [HttpGet("{id:int}/edit")] action GET 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 question
    • evenementId 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.

Snowfall.Web.Mvc/Controllers/QuestionsController.cs
// 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.

Snowfall.Web.Mvc/Views/Questions/Index.cshtml
<!-- ... -->

<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é.

http://localhost:4200

É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

  1. IQuestionRepository
    Snowfall.Data/Repositories/IQuestionRepository.cs
    public interface IQuestionRepository
    {
    Task<Question?> FindById(int id);
    Task<List<Question>> FindByEvenementIdAndUserId(int evenementId, string userId);
    Task<Question> Create(Question question);
    }
  2. IQuestionService
    Snowfall.Application/Services/IQuestionService.cs
    public interface IQuestionService
    {
    Task<Question?> FindById(int id);
    Task<List<Question>> FindByEvenementIdAndUserId(int evenementId, string userId);
    Task<Question> Create(Question question);
    }
  3. QuestionRepository. Utilisez l'aide contextuelle en cliquant sur le nom de la classe et utilisez Implement missing members. Implémentez la fonction pour obtenir une question en fonction d'un id.
    Snowfall.Data/Repositories/QuestionRepository.cs
    public 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;
    }
    }
  4. QuestionService. Idem pour le service, utilisez l'aide contextuelle en cliquant sur le nom de la classe et utilisez Implement missing members.
    Snowfall.Application/Services/QuestionService.cs
    public 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.

  1. Récupérez l'événement
    public async Task<IActionResult> Edit(int id, int evenementId)
    {
    var evenement = await _evenementService.FindById(evenementId);

    return Ok();
    }
  2. 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();
    }
  3. 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

  1. Sous Views/Questions, ajoutez une vue Edit (Razor MVC View with Layout)
  2. Associez la vue au ViewModel ModifierQuestionViewModel et changez le titre
    Snowfall.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:

SSnowfall.Web.Mvc/Views/Questions/Edit.cshtml
<!-- ... -->

<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>
info

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
Snowfall.Web.Mvc/Controllers/QuestionsController.cs
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:

http://localhost:4200

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 :(

Imgur

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!

img

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:

Snowfall.Web.Mvc/Views/Questions/Edit.cshtml
<!-- ... -->
<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.

  1. IQuestionRepository
    Snowfall.Data/Repositories/IQuestionRepository.cs
    public 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);
    }
  2. IQuestionService
    Snowfall.Application/Services/IQuestionService.cs
    public 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);
    }
  3. QuestionRepository Utilisez l'aide contextuelle en cliquant sur le nom de la classe et utilisez Implement missing members. Implémentez la fonction pour modifier une question en fonction d'un id.
    Snowfall.Data/Repositories/QuestionRepository.cs
    public 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;
    }
    }
  4. QuestionService Idem pour le service, utilisez l'aide contextuelle en cliquant sur le nom de la classe et utilisez Implement missing members.
    Snowfall.Application/Services/QuestionService.cs
    public async Task<bool> Update(Question question)
    {
    return await _questionRepository.Update(question);
    }

Sauvegarder les modifications (FINALEMENT! 🤯)

Snowfall.Web.Mvc/Controllers/QuestionsController.cs
// 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! 🎉