niedziela, 4 grudnia 2022

API ASP.NET Core - Walidacja danych wejściowych

 Dane wejściowe w API możemy sprawdzać na dwa sposoby:

  1. Korzystając z atrybutów walidacji na modelu
  2. Tworząc własną logikę walidacji w klasie Controllera

1. Stworzenie walidacji jako atrybutu modelu

Jako przykład możemy stworzyć atrybut, który sprawdzi, że wszystkie znaki ciągu teksu są wielkimi literami:

using System.ComponentModel.DataAnnotations;
using System.Globalization;
using static System.String;

namespace BookStoreApi;

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public class UpperCaseAttribute : ValidationAttribute
{
    public UpperCaseAttribute() : base("All characters for {0} field must be upper case")
    {
    }

    public override bool IsValid(object? value)
    {
        return (value is string str) && str.All(char.IsUpper);
    }

    public override string FormatErrorMessage(string name)
    {
        return Format(CultureInfo.CurrentCulture, ErrorMessageString, name);
    }
}


Ważniejsze punkty implementacji
  • klasa którą implementuje powinna dziedziczyć po ValidationAttribute
  • W konstruktorze możemy (anie nie musimy) przekazać error message, który zostanie wyświetlony użytkownikowi jeżeli walidacja się nie powiedzie. Możemy także zostawić miejsce na nazwę pola, która zostanie wstrzyknięta do wiadomości błędu - {0}
  • Metoda FormatErrorMessage zawiera parametr name, który reprezentuje sprawdzane pole
Atrybut dodajemy na pole naszego obiektu DTO i możemy sprawdzić jego działanie:

public class BookDto
{
    public int Id { get; set; }

    [Required, MaxLength(250), MinLength(3)]
    public string Name { get; set; }

    [Required, MaxLength(250), MinLength(3), UpperCase]
    public string Author { get; set; }

    public int PublishYear { get; set; }
}



2. Własna logika w klasie Controllera

Logikę walidacji można także zawrzeć bezpośrednio w klasie Controllera:

    public async Task<ActionResult<BookDto>> CreateBook([FromBody] BookDto bookDto)
    {
        if (bookDto == null)
        {
            return BadRequest(bookDto);
        }

        if (bookDto.Id > 0)
        {
            return StatusCode(StatusCodes.Status500InternalServerError);
        }

        if (!bookDto.Author.All(char.IsUpper))
        {
            ModelState.AddModelError("AuthorValidation", "All characters for Author field must be upper case ccc");
            return BadRequest(ModelState);
        }
        //... code....
     }
Po uruchomieniu aplikacji i przetestowaniu, analogicznie jak poprzednio otrzymamy błąd w przypadku gdy pole Author będzie zawierało jakiekolwiek małe litery. 

Obydwa sposoby walidacji są jak najbardziej poprawne. Drugi z jednej strony może wydawać się mniej skomplikowany (mniej kodu), jednak stworzenie atrybutu pozwala na jego łatwe re-używanie.