2015-01-26 2 views
5

Così ho un'entità nella mia Models directory:Come mantenere le regole di validazione all'interno dell'entità mentre si utilizza un modello di visualizzazione all'interno del controller?

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

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

    [Required, MaxLength(2000)] 
    public string Description { get; set; } 
} 

e voglio esporlo a vista con un ViewModel:

public class BaseEventViewModel 
{ 
    public string Name { get; set; } 

    [DataType(DataType.MultilineText)] 
    public string Description { get; set; } 
} 

public class EventCreateViewModel : BaseEventViewModel 
{ 

} 

Il mio ragionamento alla base di questo è che io voglio tutti i convalida dei dati da eseguire sull'entità e tutti gli elementi di presentazione (come il rendering di un'area di testo) da eseguire sul modello di visualizzazione. Quindi posso usare comunque molti modelli di visualizzazione che voglio rappresentare la mia entità, pur mantenendo l'integrità dei dati.

Così ho cambiato il mio controller per utilizzare la nuova vista del modello:

[HttpPost] 
    [ValidateAntiForgeryToken] 
    public ActionResult Create(EventCreateViewModel viewModel) 
    { 
     if (ModelState.IsValid) 
     { 
      db.Events.Add(new Event 
      { 
       Name = viewModel.Name, 
       Description = viewModel.Description 
      }); 
      db.SaveChanges(); 
      return RedirectToAction("Index"); 
     } 

     return View(viewModel); 
    } 

Tuttavia nessuno dei convalida entità viene fatto e sono in grado di presentare un modulo vuoto che solleva un'eccezione DbEntityValidationException.

Presumibilmente perché ModelState.IsValid funziona sul modello di visualizzazione, non sull'entità rappresentata dal modello di visualizzazione. Come posso prendere questi errori di validazione?

risposta

0

In questo caso, le regole di convalida devono essere sul modello di visualizzazione. Il lavoro che stai facendo è l'input dell'utente di validazione, ed è quello che stai usando per ricevere l'input.

È possibile eseguire una fase di convalida separata sull'entità prima di essere scritta nel database. In tal caso, si utilizzerà un meccanismo di convalida separato per gestire le regole sull'entità.

+0

Speravo ci sarebbe una soluzione più pulita a questo punto, poiché ora avrò le stesse annotazioni di dati sparse su più modelli di visualizzazione. Se ne cambio uno, devo ricordarmi di cambiarli tutti. :/ –

+0

Non necessariamente. Se non stai facendo nulla con i dati che ricevi da ViewModel, non c'è motivo di convalidare di nuovo. Devi solo convalidare se il tuo programma sta modificando l'input dell'utente. –

+2

@KingIsaac: la vista non dovrebbe essere attendibile, il controllore non dovrebbe considerare attendibile, il database non dovrebbe fidarsi del chiamante. La validazione su ogni passaggio (anche db) è una buona idea e non dovrebbe essere saltata. – fex

1

Il modello è tutto ciò che viene passato attraverso i parametri del metodo di azione. Siamo spiacenti, l'unico modo per ottenerlo è aggiungere annotazioni di dati anche in ViewModels.

EDIT: Potrebbe essere aggiunto al runtime con reflection (quindi le modifiche nella convalida Entity potrebbero essere automaticamente "viste" su ViewModels) - tuttavia è molto lavoro. Dovresti ereditare da DataAnnotationsModelValidatorProvider, ottenere tutti gli attributi dalle proprietà di classe Entity appropriate e aggiungerli a viewmodels. Penso che il modo migliore sia scrivere test unitari per le regole di convalida Entity e Viewmodels (sono gli stessi attributi aggiunti ai campi in Entity e ViewModel) - per evitare diversi bug di validatori.

Il secondo buon modo (e quello più veloce) per risolvere questo tipo di problema è utilizzare la struttura AOP come PostSharp. Creare aspetti come: EntityNameValidatorAspect (per aggiungere annotazioni di dati per la proprietà con i valori degli attributi corretti). Quindi aggiungere questo aspetto ([EntityNameValidatorAspect]) prima di denominare la proprietà in Entity e Viewmodel e così via. Questa è un'analogia con il refactoring di codice ripetuto per metodo: basta "ridefinire" alcuni attributi comuni in uno solo.

4

Ho effettivamente trovato la risposta dopo essere stato spinto nella giusta direzione. Se posso aggiungere questa annotazione al mio modello di vista, esso erediterà tutte le annotazioni applicate alle proprietà sul mio soggetto:

[MetadataType(typeof(Event))] 
public class BaseEventViewModel 
{ 
    public int Id { get; set; } 

    public string Name { get; set; } 

    [DataType(DataType.MultilineText)] 
    public string Description { get; set; } 
} 

Ora, quando invio un modulo vuoto vengo mostrato gli errori di validazione come normale.

Ciò comporta l'avvertenza di dover ridefinire nuovamente ogni proprietà all'interno del mio modello di vista, che sconfigge il punto di un modello di vista che contiene solo le proprietà richieste, tuttavia funziona per il mio caso.

+0

'Questo ha l'avvertenza di dover ridefinire nuovamente ogni proprietà all'interno del mio modello di vista. Puoi inserire un oggetto' Event' nel tuo modello di vista. Questo risolverebbe entrambi i tuoi problemi. – Shoe

+0

@Shoe e attiverà alcune terribili eccezioni ad esempio con ORM lazy loading – fex

+0

@fex Sure, ma puoi sempre fare una ricerca per una nuova entità basata sull'ID passato. Davvero un non-problema. – Shoe

0

Sto usando questo incredibile NuGet che fa le annotazioni dinamiche ExpressiveAnnotations utilizzando AOP simile modello

Si potrebbe convalidare qualsiasi logica che si può sognare:

public string Email { get; set; } 
public string Phone { get; set; } 
[RequiredIf("Email != null")] 
[RequiredIf("Phone != null")] 
[AssertThat("AgreeToContact == true")] 
public bool? AgreeToContact { get; set; }