“user may do X is user owns object Y”: Implement logic in Model Validation or Controller logic?

StackOverflow https://stackoverflow.com/questions/9337057

  •  30-04-2021
  •  | 
  •  

Question

Consider, for example's sake, the logic "A user may only edit or delete a comment that the user has authored".

My Controller Actions will repeat the logic of checking whether the currently logged in user can affect the comment. Example

[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);
}

Of course, I can bundle that logic up in a method so that I'm not copy / pasting that snippet; however, taking that code out of the controller and putting it in a ValidationAttribute keeps my Action smaller and easier to write tests for. Example

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; }
}

Is Model Validation the right tool for this job? I like taking that concern out of the controller Action; but in this case, it may complicate things further. This is especially true when you consider that this Action is part of a RESTful API and needs to return a different HTTP Status Code depending on the Validation errors in the ModelState.

Is there "best practice" in this case?

Was it helpful?

Solution

Personally, I think that it looks nice, but you are getting carried away with annotations. I think that this does not belong in your presentation layer and it should be handled by your service layer.

I would have something on the lines of:

[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
    }
} 
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top