Come evitare la doppia convalida dei dati in un'applicazione con interfaccia web?

3

Sto imparando la progettazione del software creando un'applicazione web CRUD (ASP.NET MVC con Entity Framework). L'ho diviso in due progetti: la prima è la libreria Core, che contiene la logica di business, la seconda è la Web GUI. Ho avuto un'idea, che la libreria Core separata può essere utilizzata altrove (ad esempio l'applicazione desktop) o la GUI può essere completamente cambiata in seguito, se necessario.

La logica aziendale è memorizzata in "gestori" che operano con entità di database e forniscono risultati utilizzando oggetti di trasferimento dati, perché non voglio esporre le entità db al di fuori del core. La GUI Web accetta l'input dell'utente e utilizza i manager per svolgere il lavoro. Definisce inoltre gli oggetti del modello di vista, che sono fondamentalmente copiati dagli oggetti di trasferimento dati, ma arricchiti con gli attributi di annotazione dei dati.

problema

Il progetto Web esegue la convalida lato server sugli oggetti del modello di visualizzazione, in quanto è necessario verificare l'input dell'utente. Il progetto principale esegue di nuovo la stessa convalida sugli oggetti di trasferimento dati, poiché non conosce nulla sullo strato della GUI sopra di esso. La domanda è come posso evitare la convalida dei dati duplicati, perché a volte richiede query al database e in generale aggiunge il sovraccarico e il codice duplicato. Quali sono le migliori pratiche nel mio caso? O l'idea di separare la soluzione in due progetti è sbagliata?

I post che ho letto finora discutono della validazione in normali soluzioni a 3 livelli, in cui le convalide sono leggermente diverse in ogni livello (client (javascript), business e livello dati) e tale convalida duplicata non è un problema (entrambi sono business) .

Soluzioni che ho trovato

  1. Consenti alla GUI di eseguire la convalida e di ignorarne qualsiasi in Core.
  2. Consenti ai gestori di eseguire la convalida e rilevare le eccezioni sollevate nella GUI. Ho letto che questa è una cattiva pratica [citation-needed] .
  3. Sposta la convalida all'interno degli oggetti di trasferimento dati con un flag "già validato" privato. Fornire ai manager il metodo ValidateDto che attiva la convalida su un determinato DTO. I risultati di convalida nel dizionario di messaggi di errore con nomi di proprietà come chiavi, imposta flag, ma se già impostato non fa nulla. Anche il meccanismo è necessario per rimuovere il flag se DTO è stato modificato.

Modifica

Penso di aver bisogno di chiarire: Core è una libreria di classi, il web è una normale applicazione ASP.NET MVC con viste, controller, ecc.

Modifica 2

La mia domanda sembra un duplicato di Validazione dell'input dei dati - Dove? Quanto? [chiuso] e non sono d'accordo. Questo post discute diversi ambiti di convalida su diversi livelli all'interno di un'applicazione (javascript lato client, livello aziendale, convalida livello dati). Ogni livello copre diversi ambiti di validazione, ma tendono ad intersecarsi. Mentre cerco una risposta su come rimuovere la convalida duplicata all'interno di un singolo livello (business) quando viene separata in due progetti. Anche i post collegati non hanno una risposta accettata e la risposta più votata sta fondamentalmente dicendo di spostare la convalida di base in un unico posto. Che è in definitiva corretto ma non è una risposta al mio problema.

Tutte le risposte finora suggeriscono di mettere la validazione nella libreria delle classi Core. Quindi la domanda ora è come dovrei restituire le informazioni sugli errori di input non validi.

  1. Saranno metodi speciali in "API" core che dovrebbero essere chiamati ogni volta prima che i dati vengano forniti a Core. Questa API restituirà informazioni dettagliate sugli errori che possono essere fornite all'utente. Ciò richiede che l'utente della libreria Core (programmatore) conosca tale comportamento speciale (o un sacco di codice in caso contrario, per decidere se la convalida è stata chiamata dall'utente prima o meno).

  2. Sarà un'eccezione appositamente definita che verrà lanciata da Core se l'input non è valido. Questa eccezione conterrà informazioni dettagliate sugli errori.

Soluzione

Grazie a tutti per le risposte e i commenti! Ho finito con la rimozione di tutte le convalide lato server dal progetto Web e la convalida solo all'interno del progetto Core. Ho anche creato InvalidModelException come utente Machado ha suggerito e utilizzato la proprietà Exception.Data per archiviare tutti gli errori del modello in forma di coppie chiave / valore, dove la chiave era il nome della proprietà non valida e il valore era il codice di errore precedentemente definito in Core come enum. All'interno del progetto Web sto rilevando questa eccezione InvalidModelException e una volta catturato sto riempiendo ModelState con errori di modello per fornire un feedback migliore all'utente. Sto memorizzando tutti i messaggi di errore facili da usare nel file .resx della risorsa nel progetto Web. In base al nome della proprietà e al codice di errore di InvalidModelException, sto generando la stringa ErrorMessageName per estrarre un messaggio di errore amichevole dal file di risorse.

    
posta Roman Artemov 16.05.2017 - 17:45
fonte

4 risposte

2

Il nucleo non sa da dove vengono i dati, giusto? Quindi, dal momento che tutti gli input dei dati devono essere disinfettati , direi che è necessario che il CORE convalidi gli input.

Detto questo, dovresti creare un'API in cui ti aspetti di ricevere input sbagliati e restituire qualcosa di significativo per il chiamante, e non solo lanciando eccezioni strane e criptiche ovunque. "Garbage in, garbage out" è solo una cattiva progettazione dell'API.

Un approccio che ho utilizzato in passato con alcune applicazioni MVC5 era la creazione di una "BusinessException", che conteneva alcune informazioni chiave per il chiamante per capire che stavano usando l'API errata. Uno dei componenti di BusinessException era un "messaggio di errore intuitivo", che poteva essere mostrato direttamente a un utente in un'app Forms o in un'app Web (il core non sapeva mai dove era in esecuzione), insieme ad altri importanti campi come "FaultyProperty", "TechnicalException" e così via.

In questo modo puoi rendere la tua app MVC un thin layer eseguendo una validazione di base (molto semplice) prima di inviare il carico pesante al Core e mantenere un singolo modo idiomatico per gestire gli errori catturando la "BusinessException" quando necessario e reagendo di conseguenza .

In questo modo puoi mantenere il principio di responsabilità singola e DRY sul posto mantenendo le convalide e mantenendo il codice di facile lettura.

E restituendo i dettagli dell'errore al chiamante riempiendo campi / proprietà specifici in BusinessException, il livello della GUI può evidenziare qualsiasi cosa sia necessaria per fornire un'esperienza utente piacevole.

    
risposta data 16.05.2017 - 19:21
fonte
3

Per ricapitolare / confermare: si hanno due livelli di un'applicazione (nello stesso spazio del processo) in cui il livello esterno dell'applicazione sta eseguendo le convalide eseguite anche dal livello interno. Questo non deve essere confuso con la validazione lato client che riguarda l'esperienza utente. Ci sono fondamentalmente due percorsi principali che puoi intraprendere con questo: il modo semplice, o il modo complicato.

Il modo semplice è rimuovere la convalida ridondante. Puoi rimuoverlo dallo strato interno o dallo strato esterno. Avere solo lo strato esterno non ha molto senso per me. Avere due livelli significa che puoi usare lo strato interno in qualche altra soluzione. Si potrebbe anche riutilizzare il livello esterno in teoria, ma questo è probabilmente improbabile. Se riutilizzi il livello interno con qualche altro strato esterno, significa che devi duplicare la convalida lì o in qualche modo avere la possibilità di abilitare disabilitare la convalida sul core. L'approccio più semplice consiste nel mettere tutte le convalide 'core' nel livello interno e implementare solo la validazione nel livello esterno che è specifico per quel livello.

C'è una domanda di eccezioni. Il costo delle eccezioni è spesso sovra venduto. Ho fatto alcune analisi sul costo delle eccezioni in Java e 1. È completamente dipendente dalla profondità dello stack 2. Anche con stack veramente profondi (migliaia di chiamate) ho dovuto eseguire molte iterazioni solo per ottenere misurazioni significative. Mi aspetto che sia lo stesso per C #. Ti incoraggerei a fare qualche esperimento per vedere se questo è davvero un problema. L'utilizzo di questi per il controllo del flusso in modo Pythonistic è scoraggiato, ma proteggere il tuo sistema da dati errati è un caso in cui lanciare un'eccezione mi sembra perfettamente valida. se si vuole veramente evitare questo, è possibile utilizzare un qualche tipo di oggetto associato alla richiesta per tenere traccia degli stati e dei problemi. Questa potrebbe essere una buona idea se devi gestire avvertimenti o errori non fatali.

Il modo complicato consiste nell'usare una sorta di libro mastro nella logica di convalida per verificare se ciascuna convalida richiesta è stata eseguita sulla transazione specificata. Quindi il core non ha bisogno di rieseguire quella logica. È fantastico e quindi ci sono molte opportunità per creare nuovi problemi.

    
risposta data 16.05.2017 - 19:27
fonte
0

Un'eccezione da core non è probabilmente appropriata, dal momento che potresti avere più errori di convalida indipendenti in un set di dati di input e vuoi mostrare tutti gli errori nell'interfaccia utente. Quindi penso che l'approccio migliore sia quello di avere un metodo validate(InputDataSet) nel core che restituisce una collezione di errori di validazione per i dati di input.

Potresti fare in modo che il metodo di validazione restituisca un tipo diverso, a ValidatedDataset , se la convalida ha avuto successo. Quindi puoi fare in modo che i metodi commerciali accettino solo ValidatedDataset .

    
risposta data 16.05.2017 - 19:21
fonte
0

Se si tratta di un progetto interno con piccoli team, penso che si possa tranquillamente eliminare uno dei livelli di convalida. Se il progetto è di grandi dimensioni o complesso, oppure devi gestire più team o se terze parti accederanno alle tue API, probabilmente vorrai la convalida ridondante.

Se l'atto di convalidare qualcosa ha un sovraccarico dovuto alle ricerche nel database, prendere in considerazione l'implementazione di una cache comune. Inseriresti il tuo provider di cache nella root di composizione per assicurarti che tutti i livelli della tua applicazione usassero la stessa cache comune. Quando si verifica una convalida ridondante e deve interrogare il database per alcuni valori di ricerca, si verificherà un hit della cache e l'impatto sulle prestazioni sarà minimo.

    
risposta data 17.05.2017 - 03:08
fonte

Leggi altre domande sui tag