2010-01-15 10 views
31

ho un'entità di livello di dominio standard:Mapping convalida gli attributi da Domain Entity a DTO

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

    public string Name { get; set; } 

    public decimal Price { get; set;} 
} 

che ha un qualche tipo di attributi di validazione applicati:

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

    [NotEmpty, NotShorterThan10Characters, NotLongerThan100Characters] 
    public string Name { get; set; } 

    [NotLessThan0] 
    public decimal Price { get; set;} 
} 

Come potete vedere, ho fatto su questi attributi completamente. Quale framework di convalida (NHibernate Validator, DataAnnotations, ValidationApplicationBlock, Castle Validator, ecc.) In uso qui non è importante.

Nel mio livello di cliente, ho anche una configurazione standard in cui io non uso le entità di dominio stessi, ma invece li map per ViewModels (aka DTO) che mio punto di vista strato usa:

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

    public string Name { get; set; } 

    public decimal Price { get; set;} 
} 

Diamo quindi dico che voglio che il mio client/vista sia in grado di eseguire alcune convalide di livello base.

L'unico modo che vedo posso fare questo è quello di ripetere le definizioni di convalida nell'oggetto ViewModel:

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

    // validation attributes copied from Domain entity 
    [NotEmpty, NotShorterThan10Characters, NotLongerThan100Characters] 
    public string Name { get; set; } 

    // validation attributes copied from Domain entity 
    [NotLessThan0] 
    public decimal Price { get; set;} 
} 

Questo chiaramente non è soddisfacente, la logica di business come ora ho ripetuto (convalida proprietà a livello) nel livello ViewModel (DTO).

Quindi cosa si può fare?

Supponendo che io utilizzi uno strumento di automazione come AutoMapper per mappare le entità di dominio ai miei DTO ViewModel, non sarebbe bello anche trasferire in qualche modo la logica di convalida per le proprietà mappate al ViewModel?

Le domande sono:

1) Si tratta di una buona idea?

2) Se sì, può essere fatto? In caso contrario, quali sono le alternative, se presenti?

Grazie in anticipo per qualsiasi input!

+0

EDIT: Suppongo che dovrei menzionare che sto lavorando con ASP.NET MVC. Inizialmente pensavo che questo potesse non essere rilevante, ma poi ho capito che probabilmente ci sono altri tipi di soluzioni nel mondo WinForms/WPF/Silverlight (come MVVM) che potrebbero non essere applicabili allo stack web. –

+0

Perché hai bisogno di un DTO? Perché non limitarti alla tua classe di entità? –

+3

@Josh - Al fine di stabilire un livello pulito di separazione, tra le altre ragioni. In ogni caso, penso che discutere il modello DTO sia un argomento a parte. –

risposta

11

Se stai usando qualcosa di sostenere DataAnnotations, si dovrebbe essere in grado di utilizzare una classe di metadati per contenere il vostro convalida attributi:

public class ProductMetadata 
{ 
    [NotEmpty, NotShorterThan10Characters, NotLongerThan100Characters] 
    public string Name { get; set; } 

    [NotLessThan0] 
    public decimal Price { get; set;} 
} 

e aggiungerlo nella MetadataTypeAttribute sia l'entità del dominio & DTO:

[MetadataType(typeof(ProductMetadata))] 
public class Product 

e

[MetadataType(typeof(ProductMetadata))] 
public class ProductViewModel 

Questo non funzionerà immediatamente con tutti i validatori: potrebbe essere necessario estendere il framework di convalida scelto per implementare un approccio simile.

+0

Sam, grazie per l'input. Questo sembra un buon approccio. Ho intenzione di fare ulteriori ricerche sul fatto che questo approccio ai metadati sia valido per il livello Dominio. Segnalerà qualsiasi risultato qui. –

+3

Il problema qui è che se si dispone di un ViewModel che esprime un * sottoinsieme * dei dati in un oggetto Dominio, sarà comunque necessario includere le proprietà non necessarie nel ViewModel in ogni caso, altrimenti si otterrà un errore di runtime perché alcune delle proprietà non possono essere mappate. – Jonathan

+1

@jonathonconway: se si applica un diverso insieme di regole di convalida all'oggetto Dominio e al ViewModel (a causa di un diverso insieme di attributi), metterei in dubbio il valore nel tentativo di condividerne "alcuni". Se il tuo ViewModel non assomiglia molto al tuo oggetto Dominio, IMO non è un esercizio utile per tentare di applicare una validazione identica ad entrambi. Detto questo, se stai scrivendo il tuo validatore che segue questo approccio, potresti far sì che ignori le proprietà che non esistono sull'oggetto di destinazione. – Sam

3

Perché non utilizzare un'interfaccia per esprimere le proprie intenzioni? Ad esempio:

public interface IProductValidationAttributes { 
    [NotEmpty, NotShorterThan10Characters, NotLongerThan100Characters] 
    string Name { get; set; } 

    [NotLessThan0] 
    decimal Price { get; set;} 
} 
+0

Hmm. Approccio interessante Suppongo che l'interfaccia IProductValidationAttributes sia definita nel livello Dominio? E implementato sia dall'entità del dominio del prodotto che da ProductViewModel? Se così fosse, non sarebbe questo a vanificare lo scopo di un viewmodel? Se deve implementare un'interfaccia nel livello Dominio? Se la mia vista deve prendere una dipendenza dal livello del dominio, allora potrei anche usare le entità di dominio originali, no? –

+0

-1, poiché l'interfaccia determina gli stessi tipi di dati sia in ViewModel che nel modello di dominio, ma in genere non è così. I ViewModels sono generalmente costituiti da stringhe, mentre gli oggetti di dominio contengono numeri interi, decimali, booleani, ecc. – Valentin

+0

@Martin. Se dovessi farlo, avrei l'interfaccia in un progetto separato. Ma non prenderei questo approccio. Non mi piace utilizzare gli attributi per la convalida, specialmente nel dominio (non dovresti consentire agli oggetti di dominio di immettere uno stato non valido). Penso che proverai più dolore di quanto valga la pena tentare di riutilizzare questa logica, perché non solo convalidare sul lato dominio (nel costruttore/fabbrica per invariante e nel comando per tutto il resto)? Se stai semplicemente facendo CRUD, probabilmente utilizzerei un pattern di registrazione attivo piuttosto che ddd e userei direttamente gli oggetti. – JontyMC

0

Prima di tutto, non esiste alcuna nozione di entità di dominio "standard". Per me, l'entità di dominio standard non ha setter per cominciare. Se segui questo approccio, puoi avere api più significative, che trasmettono effettivamente qualcosa sul tuo dominio. Quindi, puoi avere un servizio applicativo che elabora il tuo DTO, crea comandi che puoi eseguire direttamente contro oggetti del tuo dominio, come SetContactInfo, ChangePrice ecc. Ognuno di questi può aumentare ValidationException, che a sua volta puoi raccogliere nel tuo servizio e presentare a l'utente. Puoi ancora lasciare i tuoi attributi sulle proprietà di dto per la semplice convalida dell'attributo/livello di proprietà. Per qualsiasi altra cosa, consulta il tuo dominio. E anche se questa è un'applicazione CRUD, eviterei di esporre le mie entità di dominio al livello di presentazione.

+0

@epitka - Sono d'accordo con tutto ciò che hai detto, e questo è lo schema che sto seguendo attualmente. Tuttavia, trovo ancora che sto duplicando la convalida: i miei oggetti comando di solito hanno convalide a livello di proprietà che devo duplicare sui DTO. Essenzialmente, questo è lo stesso problema del mio post originale, ma sostituire Command for Entity e DTO per ViewModel. Devo ancora duplicare i metadati di convalida. Il male necessario, suppongo? –

9

Lo scopo della convalida è quello di garantire che i dati che arrivano nella tua applicazione soddisfino determinati criteri, tenendo conto di ciò, l'unico posto che ha senso convalidare i vincoli di proprietà, come quelli che hai identificato qui, è nel punto in cui accettare i dati da una fonte non attendibile (cioè l'utente).

È possibile utilizzare qualcosa come il "modello di denaro" per elevare la convalida nel proprio sistema di tipi di dominio e utilizzare questi tipi di dominio nel modello di visualizzazione in cui ha senso. Se si dispone di una convalida più complessa (ad esempio, si stanno esprimendo regole di business che richiedono una conoscenza maggiore di quella espressa in una singola proprietà), queste appartengono ai metodi sul modello di dominio che applicano le modifiche.

In breve, inserire gli attributi di convalida dei dati sui modelli di visualizzazione e lasciarli fuori dai modelli di dominio.

+3

Cosa succede se il mio dominio è condiviso tra più applicazioni client, ciascuna con i propri modelli di dominio? Non dovrei quindi duplicare la logica di convalida in entrambe le applicazioni client? –

+3

Supponendo che si intenda ognuno con i propri modelli di vista. Solo l'applicazione degli attributi per visualizzare i modelli è duplicata, la logica/gli attributi di convalida ecc. Possono essere condivisi tramite una libreria di convalida comune. Puoi aggiungere la convalida a livello aziendale agli oggetti del tuo dominio per assicurarti che siano validi quando persistono, ma IMHO più di quanto non stia inseguendo il riutilizzo per il gusto di farlo. – Neal

1

Se si utilizzano entità di dominio scritte a mano, perché non inserire le entità di dominio nel proprio assieme e utilizzare lo stesso assembly sia sul client che sul server. Puoi riutilizzare le stesse convalide.

+1

Le entità di dominio sono già in un assembly separato: Dominio. Il punto del modello ViewModel è che il tuo livello Dominio non viene consumato direttamente dalle tue opinioni cliente (separazione dei dubbi). –

+0

@ Martin: hai bisogno di separare le preoccupazioni? Penso che, laddove possibile, utilizzare gli oggetti Dominio invece di un ViewModel sia utile. Se c'è una mancata corrispondenza, forse avere il wrapper ViewModel o decorare l'oggetto Dominio e quindi delegare la convalida del ViewModel alla parte che convalida l'oggetto Dominio e qualsiasi convalida specifica per la Vista può essere eseguita dopo/prima. – jamiebarrow

1

Ho pensato anche a questo per un po 'di tempo. Capisco perfettamente la risposta di Brad. Tuttavia, supponiamo di voler utilizzare un altro framework di validazione adatto per l'annotazione di entità di dominio e modelli di visualizzazione.

L'unica soluzione che riesco a trovare su carta che funzioni ancora con gli attributi sarebbe quella di creare un altro attributo che "punti" alla proprietà di un'entità di dominio che si sta specchiando nel proprio modello di vista. Ecco un esempio:

// In UI as a view model. 
public class UserRegistration { 
    [ValidationDependency<Person>(x => x.FirstName)] 
    public string FirstName { get; set; } 

    [ValidationDependency<Person>(x => x.LastName)] 
    public string LastName { get; set; } 

    [ValidationDependency<Membership>(x => x.Username)] 
    public string Username { get; set; } 

    [ValidationDependency<Membership>(x => x.Password)] 
    public string Password { get; set; } 
} 

Un quadro come xVal potrebbe essere esteso per gestire questo nuovo attributo ed eseguire la convalida gli attributi della proprietà di classe di dipendenza, ma con il valore della proprietà la visualizzazione del modello. Non ho avuto il tempo di approfondire questo aspetto.

Qualche idea?

+5

Nota, questo non è possibile a causa della mancanza di generici e lambda nell'utilizzo degli attributi. – ventaur

4

Si scopre che AutoMapper potrebbe essere in grado di farlo automaticamente per noi, che è lo scenario migliore.

AutoMapper-utenti: trasferisce gli attributi di convalida a viewmodel?
http://groups.google.com/group/automapper-users/browse_thread/thread/efa1d551e498311c/db4e7f6c93a77302?lnk=gst&q=validation#db4e7f6c93a77302

Non ho ancora avuto modo di provare le soluzioni proposte, ma intendo farlo a breve.

+2

Non riesco a vedere come questo è stato implementato il post a questo link non è molto chiaro. Qualcuno può fornire ulteriori informazioni su come fare questo – ricardo

+0

sì, sono interessato anche a questo caso ... – Marko

+0

La discussione del gruppo di google termina dicendo che la patch è stata persa. Quindi non è in Automapper ora credo? :( – Narayana

Problemi correlati