“O usuário pode fazer X se o usuário possui o objeto Y”: Implementar lógica na validação do modelo ou lógica do controlador?
-
27-10-2019 - |
Pergunta
Considere, por exemplo, a lógica "Um usuário só pode editar ou excluir um comentário de sua autoria".
Minhas ações do controlador repetirão a lógica de verificar se o usuário atualmente conectado pode afetar o comentário.Exemplo
[Authorize]
public ActionResult DeleteComment(int comment_id)
{
var comment = CommentsRepository.getCommentById(comment_id);
if(comment == null)
// Cannot find comment, return bad input
return new HttpStatusCodeResult(400);
if(comment.author != User.Identity.Name)
// User not allowed to delete this comment, return Forbidden
return new HttpStatusCodeResult(403);
// Error checking passed, continue with delete action
return new HttpStatusCodeResult(200);
}
Claro, posso agrupar essa lógica em um método para não copiar / colar esse snippet;entretanto, tirar esse código do controlador e colocá-lo em um ValidationAttribute mantém meu Action menor e mais fácil de escrever testes.Exemplo
public class MustBeCommentAuthorAttribute : ValidationAttribute
{
// Import attribute for Dependency Injection
[Import]
ICommentRepository CommentRepository { get; set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
int comment_id = (int)value;
var comment = CommentsRepository.getCommentById(comment_id);
if(comment == null)
return new ValidationResult("No comment with that ID");
if(comment.author != HttpContext.Current.User.Identity.Name)
return new ValidationResult("Cannot edit this comment");
// No errors
return ValidationResult.Success;
}
}
public class DeleteCommentModel
{
[MustBeCommentAuthor]
public int comment_id { get; set; }
}
A validação de modelo é a ferramenta certa para este trabalho?Gosto de tirar essa preocupação da Ação do controlador;mas, neste caso, pode complicar ainda mais as coisas.Isso é especialmente verdadeiro quando você considera que esta ação é parte de uma API RESTful e precisa retornar um código de status HTTP diferente, dependendo dos erros de validação no ModelState.
Há "prática recomendada" neste caso?
Solução
Pessoalmente, acho que parece bom, mas você está se deixando levar pelas anotações.Acho que isso não pertence à sua camada de apresentação e deve ser tratado por sua camada de serviço.
Eu teria algo como:
[Authorize]
public ActionResult DeleteComment(int comment_id)
{
try
{
var result = CommentsService.GetComment(comment_id, Auth.Username);
// Show success to the user
}
catch(Exception e)
{
// Handle by displaying relevant message to the user
}
}