Condividi tramite


Creare un modello con convalide delle regole business

di Microsoft

Scarica il PDF

Questo è il passaggio 3 di un'esercitazione gratuita sull'applicazione "NerdDinner" che illustra come creare un'applicazione Web di piccole dimensioni, ma completa, usando ASP.NET MVC 1.

Il passaggio 3 illustra come creare un modello che è possibile usare per eseguire query e aggiornare il database per l'applicazione NerdDinner.

Se si usa ASP.NET MVC 3, è consigliabile seguire le esercitazioni Introduzione Con MVC 3 o MVC Music Store.

NerdDinner Passaggio 3: Creazione del modello

In un framework model-view-controller il termine "modello" fa riferimento agli oggetti che rappresentano i dati dell'applicazione, nonché alla logica di dominio corrispondente che integra la convalida e le regole business. Il modello è in molti modi il "cuore" di un'applicazione basata su MVC e, come si vedrà più avanti, determina fondamentalmente il comportamento di esso.

Il framework MVC ASP.NET supporta l'uso di qualsiasi tecnologia di accesso ai dati e gli sviluppatori possono scegliere tra un'ampia gamma di opzioni di dati .NET avanzate per implementare i modelli, tra cui: LINQ to Entities, LINQ to SQL, NHibernate, LLBLGen Pro, SubSonic, WilsonORM o semplicemente ADO.NET DataReaders o DataSet non elaborati.

Per l'applicazione NerdDinner si userà LINQ to SQL per creare un modello semplice che corrisponde abbastanza strettamente alla progettazione del database e aggiunge alcune regole di convalida personalizzate e business. Verrà quindi implementata una classe di repository che consente di astrarre l'implementazione della persistenza dei dati dal resto dell'applicazione e di eseguire facilmente unit test.

LINQ to SQL

LINQ to SQL è un mapper ORM (object relational mapper) fornito come parte di .NET 3.5.

LINQ to SQL offre un modo semplice per eseguire il mapping delle tabelle di database alle classi .NET rispetto a cui è possibile eseguire il codice. Per l'applicazione NerdDinner verrà usata per eseguire il mapping delle tabelle Dinners e RSVP all'interno del database alle classi Dinner e RSVP. Le colonne delle tabelle Dinners e RSVP corrispondono alle proprietà delle classi Dinner e RSVP. Ogni oggetto Dinner e RSVP rappresenta una riga separata all'interno delle tabelle Dinners o RSVP nel database.

LINQ to SQL consente di evitare di dover costruire manualmente istruzioni SQL per recuperare e aggiornare gli oggetti Dinner e RSVP con i dati del database. Verranno invece definite le classi Dinner e RSVP, il modo in cui eseguono il mapping al database e le relazioni tra di esse. LINQ to SQL si occuperà quindi della generazione della logica di esecuzione SQL appropriata da usare in fase di esecuzione durante l'interazione e l'uso.

È possibile usare il supporto del linguaggio LINQ in VB e C# per scrivere query espressive che recuperano oggetti Dinner e RSVP dal database. Ciò riduce al minimo la quantità di codice di dati che è necessario scrivere e consente di creare applicazioni davvero pulite.

Aggiunta di classi LINQ to SQL al progetto

Si inizierà facendo clic con il pulsante destro del mouse sulla cartella "Modelli" all'interno del progetto e scegliendo il comando di menu Aggiungi nuovo> elemento :

Screenshot della cartella Models. Il nuovo elemento è evidenziato. I modelli sono evidenziati e selezionati.

Verrà visualizzata la finestra di dialogo "Aggiungi nuovo elemento". Verrà filtrata in base alla categoria "Dati" e si selezionerà il modello "classi LINQ to SQL" all'interno:

Screenshot della finestra di dialogo Aggiungi nuovo elemento. I dati sono evidenziati. L I N Q to S Q L Classes è selezionato ed evidenziato.

Assegnare un nome all'elemento "NerdDinner" e fare clic sul pulsante "Aggiungi". Visual Studio aggiungerà un file NerdDinner.dbml nella directory \Models e quindi aprirà la finestra di progettazione relazionale degli oggetti LINQ to SQL:

Screenshot della finestra di dialogo Cena nerd in Visual Studio. Viene selezionato il file Nerd Dinner dot d b m l.

Creazione di classi di modelli di dati con LINQ to SQL

LINQ to SQL consente di creare rapidamente classi del modello di dati dallo schema del database esistente. A tale scopo, aprire il database NerdDinner in Esplora server e selezionare le tabelle da modellare:

Screenshot di Esplora server. Le tabelle vengono espanse. Le cene e R S V P sono evidenziate.

È quindi possibile trascinare le tabelle nell'area di progettazione LINQ to SQL. Quando si esegue questa operazione LINQ to SQL creerà automaticamente le classi Dinner e RSVP usando lo schema delle tabelle (con le proprietà della classe che eseguono il mapping alle colonne della tabella di database):

Screenshot della finestra di dialogo Cena nerd. Vengono visualizzate le classi Dinner e R S V P.

Per impostazione predefinita, la finestra di progettazione LINQ to SQL "pluralizza" automaticamente i nomi di tabella e colonna quando crea classi basate su uno schema di database. Ad esempio: la tabella "Dinners" nell'esempio precedente ha restituito una classe "Dinner". Questa denominazione di classe consente di rendere i modelli coerenti con le convenzioni di denominazione .NET e in genere si ritiene che la soluzione della finestra di progettazione sia utile (soprattutto quando si aggiungono molte tabelle). Se non si preferisce il nome di una classe o di una proprietà generata dalla finestra di progettazione, tuttavia, è sempre possibile eseguirne l'override e modificarlo in qualsiasi nome desiderato. A tale scopo, è possibile modificare il nome di entità/proprietà in linea all'interno della finestra di progettazione o modificandolo tramite la griglia delle proprietà.

Per impostazione predefinita, la finestra di progettazione di LINQ to SQL controlla anche le relazioni chiave primaria/chiave esterna delle tabelle e, in base a esse, crea automaticamente le "associazioni di relazione" predefinite tra le diverse classi di modello create. Ad esempio, quando le tabelle Dinners e RSVP sono state trascinate nella finestra di progettazione LINQ to SQL un'associazione di relazione uno-a-molti tra i due è stata dedotta in base al fatto che la tabella RSVP ha una chiave esterna alla tabella Dinners (questa è indicata dalla freccia nella finestra di progettazione):

Screenshot delle tabelle Dinner e R S V P. Una freccia viene evidenziata e puntata dall'albero delle proprietà Dinner e dall'albero delle proprietà R S V P.

L'associazione precedente causerà LINQ to SQL aggiungere una proprietà "Dinner" fortemente tipizzata alla classe RSVP che gli sviluppatori possono usare per accedere alla cena associata a un determinato RSVP. Inoltre, la classe Dinner avrà una proprietà di raccolta "RSVPs" che consente agli sviluppatori di recuperare e aggiornare gli oggetti RSVP associati a una determinata cena.

Di seguito è possibile visualizzare un esempio di intellisense in Visual Studio quando si crea un nuovo oggetto RSVP e lo si aggiunge a un insieme RSVPs di Dinner. Si noti che LINQ to SQL aggiunto automaticamente un insieme "RSVPs" nell'oggetto Dinner:

Screenshot di IntelliSense in Visual Studio. R S V Ps è evidenziato.

Aggiungendo l'oggetto RSVP all'insieme RSVPs di Dinner, viene indicato LINQ to SQL associare una relazione di chiave esterna tra la riga Dinner e RSVP nel database:

Screenshot dell'oggetto R S V P e della raccolta R S V P di Dinner.

Se non si preferisce il modo in cui la finestra di progettazione ha modellato o denominato un'associazione di tabella, è possibile eseguirne l'override. È sufficiente fare clic sulla freccia di associazione all'interno della finestra di progettazione e accedere alle relative proprietà tramite la griglia delle proprietà per rinominarla, eliminarla o modificarla. Per l'applicazione NerdDinner, tuttavia, le regole di associazione predefinite funzionano bene per le classi del modello di dati che stiamo creando e possiamo usare solo il comportamento predefinito.

Classe NerdDinnerDataContext

Visual Studio creerà automaticamente classi .NET che rappresentano i modelli e le relazioni di database definiti tramite la finestra di progettazione LINQ to SQL. Viene generata anche una classe LINQ to SQL DataContext per ogni file della finestra di progettazione LINQ to SQL aggiunto alla soluzione. Poiché è stato denominato l'elemento della classe LINQ to SQL "NerdDinner", la classe DataContext creata verrà denominata "NerdDinnerDataContext". Questa classe NerdDinnerDataContext è il modo principale per interagire con il database.

La classe NerdDinnerDataContext espone due proprietà, "Dinners" e "RSVPs", che rappresentano le due tabelle modellate all'interno del database. È possibile usare C# per scrivere query LINQ su tali proprietà per eseguire query e recuperare oggetti Dinner e RSVP dal database.

Il codice seguente illustra come creare un'istanza di un oggetto NerdDinnerDataContext ed eseguire una query LINQ su di essa per ottenere una sequenza di Dinners che si verificano in futuro. Visual Studio offre l'intellisense completo durante la scrittura della query LINQ e gli oggetti restituiti sono fortemente tipizzati e supportano anche intellisense:

Screenshot di Visual Studio. La descrizione è evidenziata.

Oltre a consentirci di eseguire una query per gli oggetti Dinner e RSVP, un NerdDinnerDataContext tiene traccia automaticamente anche di eventuali modifiche apportate successivamente agli oggetti Dinner e RSVP recuperati. È possibile usare questa funzionalità per salvare facilmente le modifiche nel database, senza dover scrivere codice di aggiornamento SQL esplicito.

Ad esempio, il codice seguente illustra come usare una query LINQ per recuperare un singolo oggetto Dinner dal database, aggiornare due delle proprietà Dinner e quindi salvare nuovamente le modifiche nel database:

NerdDinnerDataContext db = new NerdDinnerDataContext();

// Retrieve Dinner object that reprents row with DinnerID of 1
Dinner dinner = db.Dinners.Single(d => d.DinnerID == 1);

// Update two properties on Dinner 
dinner.Title = "Changed Title";
dinner.Description = "This dinner will be fun";

// Persist changes to database
db.SubmitChanges();

L'oggetto NerdDinnerDataContext nel codice precedente ha automaticamente rilevato le modifiche apportate alla proprietà apportate all'oggetto Dinner recuperato. Quando è stato chiamato il metodo "SubmitChanges()", verrà eseguita un'istruzione SQL "UPDATE" appropriata nel database per rendere persistenti i valori aggiornati.

Creazione di una classe DinnerRepository

Per le applicazioni di piccole dimensioni, talvolta è consigliabile che i controller funzionino direttamente su una classe DataContext LINQ to SQL e incorporare query LINQ all'interno dei controller. Man mano che le applicazioni aumentano, tuttavia, questo approccio diventa complesso da gestire e testare. Può anche comportare la duplicazione delle stesse query LINQ in più posizioni.

Un approccio che consente di semplificare la manutenzione e il test delle applicazioni consiste nell'usare un modello di "repository". Una classe del repository consente di incapsulare la logica di query e persistenza dei dati ed estrae i dettagli di implementazione della persistenza dei dati dall'applicazione. Oltre a rendere più pulito il codice dell'applicazione, l'uso di un modello di repository può semplificare la modifica delle implementazioni di archiviazione dei dati in futuro e può facilitare il testing unità di un'applicazione senza richiedere un database reale.

Per l'applicazione NerdDinner si definirà una classe DinnerRepository con la firma seguente:

public class DinnerRepository {

    // Query Methods
    public IQueryable<Dinner> FindAllDinners();
    public IQueryable<Dinner> FindUpcomingDinners();
    public Dinner             GetDinner(int id);

    // Insert/Delete
    public void Add(Dinner dinner);
    public void Delete(Dinner dinner);

    // Persistence
    public void Save();
}

Nota: più avanti in questo capitolo si estrae un'interfaccia IDinnerRepository da questa classe e si abilita l'inserimento delle dipendenze con esso nei controller. Per iniziare, però, inizieremo semplice e lavoreremo direttamente con la classe DinnerRepository.

Per implementare questa classe, fare clic con il pulsante destro del mouse sulla cartella "Modelli" e scegliere il comando di menu Aggiungi nuovo> elemento . Nella finestra di dialogo "Aggiungi nuovo elemento" selezionare il modello "Classe" e denominare il file "DinnerRepository.cs":

Screenshot della cartella Models. L'opzione Aggiungi nuovo elemento è evidenziata.

È quindi possibile implementare la classe DinnerRepository usando il codice seguente:

public class DinnerRepository {
 
    private NerdDinnerDataContext db = new NerdDinnerDataContext();

    //
    // Query Methods

    public IQueryable<Dinner> FindAllDinners() {
        return db.Dinners;
    }

    public IQueryable<Dinner> FindUpcomingDinners() {
        return from dinner in db.Dinners
               where dinner.EventDate > DateTime.Now
               orderby dinner.EventDate
               select dinner;
    }

    public Dinner GetDinner(int id) {
        return db.Dinners.SingleOrDefault(d => d.DinnerID == id);
    }

    //
    // Insert/Delete Methods

    public void Add(Dinner dinner) {
        db.Dinners.InsertOnSubmit(dinner);
    }

    public void Delete(Dinner dinner) {
        db.RSVPs.DeleteAllOnSubmit(dinner.RSVPs);
        db.Dinners.DeleteOnSubmit(dinner);
    }

    //
    // Persistence 

    public void Save() {
        db.SubmitChanges();
    }
}

Recupero, aggiornamento, inserimento ed eliminazione tramite la classe DinnerRepository

Dopo aver creato la classe DinnerRepository, verranno esaminati alcuni esempi di codice che illustrano le attività comuni che è possibile eseguire con esso:

Esempi di query

Il codice seguente recupera una singola cena usando il valore DinnerID:

DinnerRepository dinnerRepository = new DinnerRepository();

// Retrieve specific dinner by its DinnerID
Dinner dinner = dinnerRepository.GetDinner(5);

Il codice seguente recupera tutte le cene imminenti e le scorre:

DinnerRepository dinnerRepository = new DinnerRepository();

// Retrieve all upcoming Dinners
var upcomingDinners = dinnerRepository.FindUpcomingDinners();

// Loop over each upcoming Dinner and print out its Title
foreach (Dinner dinner in upcomingDinners) {
   Response.Write("Title" + dinner.Title);
}

Esempi di inserimento e aggiornamento

Il codice seguente illustra l'aggiunta di due nuove cene. Le aggiunte o le modifiche apportate al repository non vengono sottoposte a commit nel database finché non viene chiamato il metodo "Save()". LINQ to SQL esegue automaticamente il wrapping di tutte le modifiche in una transazione di database, pertanto tutte le modifiche vengono apportate o nessuna di esse viene eseguita quando il repository salva:

DinnerRepository dinnerRepository = new DinnerRepository();

// Create First Dinner
Dinner newDinner1 = new Dinner();
newDinner1.Title = "Dinner with Scott";
newDinner1.HostedBy = "ScotGu";
newDinner1.ContactPhone = "425-703-8072";

// Create Second Dinner
Dinner newDinner2 = new Dinner();
newDinner2.Title = "Dinner with Bill";
newDinner2.HostedBy = "BillG";
newDinner2.ContactPhone = "425-555-5151";

// Add Dinners to Repository
dinnerRepository.Add(newDinner1);
dinnerRepository.Add(newDinner2);

// Persist Changes
dinnerRepository.Save();

Il codice seguente recupera un oggetto Dinner esistente e modifica due proprietà su di esso. Le modifiche vengono sottoposte a commit nel database quando viene chiamato il metodo "Save()" nel repository:

DinnerRepository dinnerRepository = new DinnerRepository();

// Retrieve specific dinner by its DinnerID
Dinner dinner = dinnerRepository.GetDinner(5);

// Update Dinner properties
dinner.Title = "Update Title";
dinner.HostedBy = "New Owner";

// Persist changes
dinnerRepository.Save();

Il codice seguente recupera una cena e quindi aggiunge un RSVP. A tale scopo, viene usata l'insieme RSVPs nell'oggetto Dinner che LINQ to SQL creato per l'utente (perché esiste una relazione chiave primaria/chiave esterna tra i due nel database). Questa modifica viene resa persistente nel database come nuova riga di tabella RSVP quando viene chiamato il metodo "Save()" nel repository:

DinnerRepository dinnerRepository = new DinnerRepository();

// Retrieve specific dinner by its DinnerID
Dinner dinner = dinnerRepository.GetDinner(5);

// Create a new RSVP object
RSVP myRSVP = new RSVP();
myRSVP.AttendeeName = "ScottGu";

// Add RSVP to Dinner's RSVP Collection
dinner.RSVPs.Add(myRSVP);

// Persist changes
dinnerRepository.Save();

Esempio di eliminazione

Il codice seguente recupera un oggetto Dinner esistente e lo contrassegna per essere eliminato. Quando viene chiamato il metodo "Save()" nel repository, eseguirà il commit dell'eliminazione nel database:

DinnerRepository dinnerRepository = new DinnerRepository();

// Retrieve specific dinner by its DinnerID
Dinner dinner = dinnerRepository.GetDinner(5);

// Mark dinner to be deleted
dinnerRepository.Delete(dinner);

// Persist changes
dinnerRepository.Save();

Integrazione della convalida e della logica delle regole di business con le classi di modello

L'integrazione della convalida e della logica della regola business è una parte fondamentale di qualsiasi applicazione che funziona con i dati.

Convalida dello schema

Quando le classi del modello vengono definite tramite la finestra di progettazione LINQ to SQL, i tipi di dati delle proprietà nelle classi del modello di dati corrispondono ai tipi di dati della tabella di database. Ad esempio: se la colonna "EventDate" nella tabella Dinners è "datetime", la classe del modello di dati creata da LINQ to SQL sarà di tipo "DateTime" (che è un tipo di dati .NET predefinito). Ciò significa che si otterranno errori di compilazione se si tenta di assegnare un numero intero o un valore booleano al codice e verrà generato automaticamente un errore se si tenta di convertire in modo implicito un tipo di stringa non valido in fase di esecuzione.

LINQ to SQL gestisce automaticamente anche l'escape dei valori SQL quando si usano stringhe, che consente di proteggersi dagli attacchi SQL injection quando viene usato.

Convalida e logica delle regole di business

La convalida dello schema è utile come primo passaggio, ma è raramente sufficiente. La maggior parte degli scenari reali richiede la possibilità di specificare una logica di convalida più completa in grado di estendersi su più proprietà, eseguire codice e spesso tenere presente lo stato di un modello( ad esempio: viene creato /aggiornato/eliminato o all'interno di uno stato specifico del dominio, ad esempio "archiviato"). Esistono diversi modelli e framework che possono essere usati per definire e applicare regole di convalida alle classi di modello e sono disponibili diversi framework basati su .NET che possono essere usati per facilitare questa operazione. È possibile usare praticamente qualsiasi di esse all'interno di ASP.NET applicazioni MVC.

Ai fini dell'applicazione NerdDinner, si userà un modello relativamente semplice e semplice in cui si espone una proprietà IsValid e un metodo GetRuleViolations() nell'oggetto modello Dinner. La proprietà IsValid restituirà true o false a seconda che le regole di convalida e business siano tutte valide. Il metodo GetRuleViolations() restituirà un elenco di eventuali errori di regola.

Verranno implementati IsValid e GetRuleViolations() per il modello Dinner aggiungendo una "classe parziale" al progetto. Le classi parziali possono essere usate per aggiungere metodi/proprietà/eventi alle classi gestite da una finestra di progettazione di Visual Studio (ad esempio la classe Dinner generata dalla finestra di progettazione LINQ to SQL) e per evitare che lo strumento si confonda con il codice. È possibile aggiungere una nuova classe parziale al progetto facendo clic con il pulsante destro del mouse sulla cartella \Models e quindi scegliendo il comando di menu "Aggiungi nuovo elemento". È quindi possibile scegliere il modello "Classe" nella finestra di dialogo "Aggiungi nuovo elemento" e denominarlo Dinner.cs.

Screenshot della cartella Models. È selezionata l'opzione Aggiungi nuovo elemento. Il punto cena c s è scritto nella finestra di dialogo Aggiungi nuovo elemento.

Facendo clic sul pulsante "Aggiungi" si aggiungerà un file Dinner.cs al progetto e lo si aprirà all'interno dell'IDE. È quindi possibile implementare un framework di imposizione della regola/convalida di base usando il codice seguente:

public partial class Dinner {

    public bool IsValid {
        get { return (GetRuleViolations().Count() == 0); }
    }

    public IEnumerable<RuleViolation> GetRuleViolations() {
        yield break;
    }

    partial void OnValidate(ChangeAction action) {
        if (!IsValid)
            throw new ApplicationException("Rule violations prevent saving");
    }
}

public class RuleViolation {

    public string ErrorMessage { get; private set; }
    public string PropertyName { get; private set; }

    public RuleViolation(string errorMessage, string propertyName) {
        ErrorMessage = errorMessage;
        PropertyName = propertyName;
    }
}

Alcune note sul codice precedente:

  • La classe Dinner è preceduta da una parola chiave "parziale", il che significa che il codice contenuto all'interno verrà combinato con la classe generata/gestita dalla finestra di progettazione LINQ to SQL e compilata in una singola classe.
  • La classe RuleViolation è una classe helper che verrà aggiunta al progetto che consente di fornire altri dettagli su una violazione della regola.
  • Il metodo Dinner.GetRuleViolations() fa sì che le regole di convalida e business vengano valutate (verranno implementate a breve). Restituisce quindi una sequenza di oggetti RuleViolation che forniscono altri dettagli sugli errori delle regole.
  • La proprietà Dinner.IsValid fornisce una proprietà helper utile che indica se l'oggetto Dinner ha regole attive. Può essere controllato in modo proattivo da uno sviluppatore usando l'oggetto Dinner in qualsiasi momento (e non genera un'eccezione).
  • Il metodo parziale Dinner.OnValidate() è un hook che LINQ to SQL fornisce che consente di ricevere una notifica ogni volta che l'oggetto Dinner sta per essere salvato in modo permanente all'interno del database. L'implementazione di OnValidate() precedente garantisce che la cena non abbia ruleViolations prima che venga salvata. Se si trova in uno stato non valido, genera un'eccezione, che causerà LINQ to SQL interrompere la transazione.

Questo approccio offre un framework semplice in cui è possibile integrare regole di convalida e business. Per ora aggiungere le regole seguenti al metodo GetRuleViolations():

public IEnumerable<RuleViolation> GetRuleViolations() {

    if (String.IsNullOrEmpty(Title))
        yield return new RuleViolation("Title required","Title");

    if (String.IsNullOrEmpty(Description))
        yield return new RuleViolation("Description required","Description");

    if (String.IsNullOrEmpty(HostedBy))
        yield return new RuleViolation("HostedBy required", "HostedBy");

    if (String.IsNullOrEmpty(Address))
        yield return new RuleViolation("Address required", "Address");

    if (String.IsNullOrEmpty(Country))
        yield return new RuleViolation("Country required", "Country");

    if (String.IsNullOrEmpty(ContactPhone))
        yield return new RuleViolation("Phone# required", "ContactPhone");

    if (!PhoneValidator.IsValidNumber(ContactPhone, Country))
        yield return new RuleViolation("Phone# does not match country", "ContactPhone");

    yield break;
}

Viene usata la funzionalità "yield return" di C# per restituire una sequenza di qualsiasi RuleViolations. La prima sei regola controlla semplicemente che le proprietà stringa nella cena non possono essere null o vuote. L'ultima regola è un po' più interessante e chiama un metodo helper PhoneValidator.IsValidNumber() che è possibile aggiungere al progetto per verificare che il formato numero ContactPhone corrisponda al paese/area geografica della cena.

È possibile usare . Supporto delle espressioni regolari di NET per implementare questo supporto per la convalida telefonica. Di seguito è riportata una semplice implementazione phoneValidator che è possibile aggiungere al progetto che consente di aggiungere controlli del modello Regex specifici del paese/area geografica:

public class PhoneValidator {

    static IDictionary<string, Regex> countryRegex = new Dictionary<string, Regex>() {
           { "USA", new Regex("^[2-9]\\d{2}-\\d{3}-\\d{4}$")},
           { "UK", new Regex("(^1300\\d{6}$)|(^1800|1900|1902\\d{6}$)|(^0[2|3|7|8]{1}[0-9]{8}$)|(^13\\d{4}$)|(^04\\d{2,3}\\d{6}$)")},
           { "Netherlands", new Regex("(^\\+[0-9]{2}|^\\+[0-9]{2}\\(0\\)|^\\(\\+[0-9]{2}\\)\\(0\\)|^00[0-9]{2}|^0)([0-9]{9}$|[0-9\\-\\s]{10}$)")},
    };

    public static bool IsValidNumber(string phoneNumber, string country) {

        if (country != null && countryRegex.ContainsKey(country))
            return countryRegex[country].IsMatch(phoneNumber);
        else
            return false;
    }

    public static IEnumerable<string> Countries {
        get {
            return countryRegex.Keys;
        }
    }
}

Gestione delle violazioni della convalida e della logica di business

Dopo aver aggiunto il codice della convalida e della regola business precedente, ogni volta che si tenta di creare o aggiornare una cena, le regole della logica di convalida verranno valutate e applicate.

Gli sviluppatori possono scrivere codice simile al seguente per determinare in modo proattivo se un oggetto Dinner è valido e recuperare un elenco di tutte le violazioni in esso contenute senza generare eccezioni:

Dinner dinner = dinnerRepository.GetDinner(5);

dinner.Country = "USA";
dinner.ContactPhone = "425-555-BOGUS";

if (!dinner.IsValid) {

    var errors = dinner.GetRuleViolations();
    
    // do something to fix the errors
}

Se si tenta di salvare una cena in uno stato non valido, verrà generata un'eccezione quando si chiama il metodo Save() in DinnerRepository. Ciò si verifica perché LINQ to SQL chiama automaticamente il metodo parziale Dinner.OnValidate() prima di salvare le modifiche della cena e il codice è stato aggiunto a Dinner.OnValidate() per generare un'eccezione se esistono violazioni delle regole nella cena. È possibile intercettare questa eccezione e recuperare in modo reattivo un elenco delle violazioni da correggere:

Dinner dinner = dinnerRepository.GetDinner(5);

try {

    dinner.Country = "USA";
    dinner.ContactPhone = "425-555-BOGUS";

    dinnerRepository.Save();
}
catch {

    var errors = dinner.GetRuleViolations();

    // do something to fix errors
}

Poiché le regole di convalida e business vengono implementate all'interno del livello del modello e non all'interno del livello dell'interfaccia utente, verranno applicate e usate in tutti gli scenari all'interno dell'applicazione. In seguito è possibile modificare o aggiungere regole business e avere tutto il codice che funziona con gli oggetti Dinner li rispetta.

Avere la flessibilità di modificare le regole business in un'unica posizione, senza avere queste modifiche in tutta l'applicazione e la logica dell'interfaccia utente, è un segno di un'applicazione ben scritta e un vantaggio che un framework MVC aiuta a incoraggiare.

passaggio successivo

È ora disponibile un modello che è possibile usare per eseguire query e aggiornare il database.

Aggiungere ora alcuni controller e visualizzazioni al progetto che è possibile usare per creare un'esperienza dell'interfaccia utente HTML.