Using DataAnnotation Model Validation in Minimal Api

297 Views Asked by At

I am porting an application from Asp.net Controllers to Asp.net Minimal-Apis. The current project is using model-based DataAnnotations. Controllers do model validation out of the box but MinApi does not.

For an example such as below, what is the best way to do DataAnnotation model validation in MinimalApi?

Example Data Annotation Model:

using System.ComponentModel.DataAnnotations;

namespace minApi.Models;

public class Account
{
  [Required]
  public int AccountId { get; set; }

  [Required, MaxLength(50)]
  public string AccountName { get; set; };

  [Required, EmailAddress]
  public string AccountEmail { get; set; };

  [Required, Phone]
  public string AccountPhone { get; set; };

  [Required, MaxLength(50)]
  public string StreetAddress { get; set; };

  [Required, MaxLength(50)]
  public string City { get; set; };

  [Required, MaxLength(2)]
  public string StateProvince { get; set; };

  [Required, MaxLength(10)]
  public string PostalCode { get; set; };

  public bool IsActive { get; set; }true;

  public override string ToString() => $"{AccountName} AccountId: {AccountId}";
}

Example Minimal-Api With Model:

accounts.MapPost("/saveAccount", (IAccountManager _accountManager, [FromBody] Account account) =>
{
    var acct = _accountManager.SaveAccount(account);

    return Results.Ok(acct);
})
1

There are 1 best solutions below

0
On BEST ANSWER

Here is what I came up with to validate DataAnnotation models in Minimal-Apis. Please post any other suggestions to improve or better alternatives.

Helper Extension Method:

using System.ComponentModel.DataAnnotations;

namespace minApi.Helpers.Extensions;

public static class DataValidator
{
    public static (List<ValidationResult> Results, bool IsValid) DataAnnotationsValidate(this object model)
    {
       var results = new List<ValidationResult>();
       var context = new ValidationContext(model);

       var isValid = Validator.TryValidateObject(model, context, results, true);

       return (results, isValid);
   }
}

Validator Helper:

using Microsoft.AspNetCore.Mvc;
using minApi.Helpers.Extensions;
using System.Reflection;

namespace minApi.Helpers;

public static class CustomRouteHandlerBuilder
{

  public static RouteHandlerBuilder Validate<T>(this RouteHandlerBuilder builder, bool firstErrorOnly = true)
  { 
     builder.AddEndpointFilter(async (invocationContext, next) =>
     {
        var argument = invocationContext.Arguments.OfType<T>().FirstOrDefault();
        var response = argument.DataAnnotationsValidate();

        if (!response.IsValid)
        {
            string errorMessage =   firstErrorOnly ? 
                                    response.Results.FirstOrDefault().ErrorMessage : 
                                    string.Join("|", response.Results.Select(x => x.ErrorMessage));

            return Results.Problem(errorMessage, statusCode: 400);
        }

        return await next(invocationContext);
     });

     return builder;
  }

}

Validation Code Added to Minimal-Api Method (the last line):

accounts.MapPost("/saveAccount", (IAccountManager _accountManager, [FromBody] Account account) =>
{
    var acct = _accountManager.SaveAccount(account);

    return Results.Ok(acct);
})      
.Validate<Account>();