January 30, 2013

Simple PATCH validation in ASP.NET Web API

Occasionally we need to issue PATCH requests against a resource.

We recently changed the way in which a cover image was specified for a publication in the Fabrik API. Previously we were using the PATCH verb to move media for a publication. Our controller looked something like this:

public void Patch(int siteId, int postId, MoveMediaCommand command)
{
	var post = session.TryLoadFromSite<Domain.Post>(siteId, postId);
	post.MoveMedia(command.MediaItemId, command.Position);
}

We could therefore issue the following request to make a media item the default for a publication (first one in the collection):

PATCH /sites/1/posts/10/media
{
	mediaitemid: 10,
	position: 0
}

In this case, position was a required parameter and the API would return a 400 error if it was not set.

Now I needed to support passing another parameter. I wanted to allow either parameter to be set in the request body but validate at least one was present.

I could have done that validation at the controller level but even better, we can add custom validation to our model by implementing System.ComponentModel.DataAnnotations.IValidateableObject (thanks to Tugberk for reminding of me this fact):

[DataContract]
public class PatchMediaCommand : IValidatableObject
{
    [DataMember(IsRequired = true)]
    [Required(ErrorMessage = "The media item identifier is required.")]
    [Display(Name = "Media Item Id", Description = "Required. The id of the media item you wish to move.")]
    public virtual int MediaItemId { get; set; }

    [DataMember]
    [Display(Name = "Position", Description = "Required. The 0-based new position of the media item.")]
    public virtual int? Position { get; set; }

    [DataMember]
    [Display(Name = "Make Cover Image",  Description = "Whether to make the media item the cover image for the publication.")]
    public virtual bool? MakeCoverImage { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (!(Position.HasValue || MakeCoverImage.HasValue))
        {
            yield return new ValidationResult("You must set either the Position or MakeCoverImage parameters.");
        }
    }
}

Now, if neither parameter is present in the request body the ASP.NET Web API model state validation will automatically kick in and I’ll get a 400 (Bad Request) error as expected:

{
	"message": "The request is invalid.",
	"modelState": {
		"command":[ 
			"You must set either the Position or MakeCoverImage parameters."
		]
	}
}

© 2022 Ben Foster