Convalida multilivello in C #

4

Ho un progetto della console che legge gli input dal file CSV e prova a salvarli nel database.

Per questo, ho creato una classe Person che mappa una riga CSV.

Il file CSV ha due colonne Name e Age . La classe Person è come.

class Person
{
    public string Name;
    public int Age;
}

Quindi l'elenco di tutti gli oggetti popolati è List<Person> .

Ho un nuovo requisito per visualizzare i messaggi di convalida per la console prima di procedere con il salvataggio degli oggetti popolati nel database.

La convalida ha due livelli: Errore e Avviso .

Ad esempio se la proprietà Name contiene un carattere speciale, devo visualizzare questo messaggio: " Errore : Nome contiene carattere speciale"

Nel caso in cui Name contenga correttamente un carattere numerico, devo visualizzare solo un messaggio di avviso: " Avviso : il nome contiene un carattere numerico"

Stavo pensando di utilizzare DataAnnotation ma non riesco a vedere un modo per aggiungere diversi livelli (errore e avviso) al processo di convalida. Inoltre, non sono sicuro che DataAnnotation si adatti solo alle applicazioni Web.

C'è un modo per aggiungere alcune funzionalità alla classe Person per ottenere questa convalida eseguita per ogni proprietà?

NB: questo è solo un esempio per capire meglio la domanda, ho altre regole per altre proprietà.

    
posta Mhd 07.11.2017 - 21:50
fonte

2 risposte

4

Ecco la mia soluzione. Non è perfetto, ma è un inizio.

Prima di tutto, ho creato un enum chiamato ErrorLevel :

enum ErrorLevel
{
    Error,
    Warning
}

Successivamente, per ogni regola di convalida, ho creato un attributo personalizzato. Ogni attributo personalizzato ha un campo privato ErrorLevel e un metodo Validate che restituisce la descrizione dell'errore o null se non è stato trovato alcun errore (ho creato un'interfaccia IValidationRule<T> per garantire che questo metodo esista).

IValidationRule < T >

interface IValidationRule<T>
{
    string Validate(T input);
}

NoNumbersAttribute:

class NoNumbersAttribute : Attribute, IValidationRule<Person>
{
    private ErrorLevel errorLevel;

    public NoNumbersAttribute(ErrorLevel errorLevel)
    {
        this.errorLevel = errorLevel;
    }

    public string Validate(Person person)
    {
        if(person.Name.Any(c => char.IsDigit(c)))
        {
            return $"{errorLevel.ToString()}: 'Name' contains numeric characters. ";
        }

        return null;
    }
}

NoSpecialCharactersAttribute:

class NoSpecialCharactersAttribute : Attribute, IValidationRule<Person>
{
    private ErrorLevel errorLevel;

    public NoSpecialCharactersAttribute(ErrorLevel errorLevel)
    {
        this.errorLevel = errorLevel;
    }

    public string Validate(Person person)
    {
        if (!new Regex("^[a-zA-Z0-9 ]*$").IsMatch(person.Name))
        {
            return $"{errorLevel.ToString()}: 'Name' contains special character.";
        }

        return null;
    }
}

Si noti che utilizziamo errorLevel per garantire che l'output conterrà il livello di errore corretto.

Ora, nella classe Person ho cambiato i campi in proprietà poiché in realtà non vuoi campi pubblici. Ho aggiunto gli attributi personalizzati sopra le proprietà pertinenti:

Persona:

class Person
{
    [NoSpecialCharacters(ErrorLevel.Error)]
    [NoNumbers(ErrorLevel.Warning)]
    public string Name { get; }

    public int Age { get; }

    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }
}

E infine, ho creato un metodo che ottiene un elenco di persone, ottiene i loro risultati di convalida e li stampa:

ValidatePeople:

static bool ValidatePeople(List<Person> people)
{
    bool isValid = true;

    foreach (Person person in people)
    {
        PropertyInfo[] properties = typeof(Person).GetProperties();
        foreach (PropertyInfo property in properties)
        {
            var rules = Array.ConvertAll(property.GetCustomAttributes(typeof(IValidationRule<Person>), true), item => (IValidationRule<Person>)item);
            var failures = rules.Select(r => r.Validate(person)).Where(f => f != null);

            failures.ToList().ForEach(f => Console.WriteLine($"({person.Name}) {f}"));
            if (failures.Count() > 0) isValid = false;
        }
    }

    return isValid;
}

Quindi il metodo Main sarà simile a questo:

static void Main(string[] args)
{
    List<Person> people = GetPeopleFromCSV();
    if(ValidatePeople(people))
    {
        InsertPeopleToDatabase();
    }  
}
    
risposta data 08.11.2017 - 11:50
fonte
1

Ci sono molti modi in cui puoi andare.

Gli oggetti business possono implementare IValidate che è possibile definire come:

public interface IValidate
{
    List<IWarning> GetWarnings();
    List<IError> GetErrors();
}

E così aggiungi la convalida alle classi.

Potresti creare validatori come

public abstract class Validator
{
    public abstract List<IWarning> GetWarnings();
    public abstract List<IError> GetErrors();
}
public class PersonValidator : Validator
{
    private IPerson _Person;
    public PersonValidator(IPerson person)
    {
         _Person = person;
    }
    public override List<IWarning> GetWarnings() { /* logic here */ }
    public override List<IError> GetErrors() { /* logic here */ }
}

Quando implementi la logica nel validatore, puoi anche leggere DataAnnotations o i tuoi attributi per l'annotazione.

vale a dire. puoi creare le tue annotazioni che indicano il livello:

[AttributeUsage(AttributeTargets.Property)]
public class MyValidationAttribute : Attribute
{
    public Level Level {get;} // error, warning, ...
    public string AllowedCharacters {get;}
    // don't forget the ctor 
}
    
risposta data 08.11.2017 - 10:45
fonte

Leggi altre domande sui tag