Alessandro Del Sole's Blog

{ A programming space about Microsoft® .NET® }
posts - 1908, comments - 2047, trackbacks - 352

My Links

News

Your host

This is me! Questo spazio è dedicato a Microsoft® .NET®, di cui sono molto appassionato :-)

Cookie e Privacy

Disabilita cookie ShinyStat

Microsoft MVP

My MVP Profile

Microsoft Certified Professional

Microsoft Specialist

Xamarin Certified Mobile Developer

Il mio libro su VB 2015!

Pre-ordina il mio libro su VB 2015 Pre-ordina il mio libro "Visual Basic 2015 Unleashed". Clicca sulla copertina per informazioni!

Il mio libro su WPF 4.5.1!

Clicca sulla copertina per informazioni! E' uscito il mio libro "Programmare con WPF 4.5.1". Clicca sulla copertina per informazioni!

These postings are provided 'AS IS' for entertainment purposes only with absolutely no warranty expressed or implied and confer no rights.
If you're not an Italian user, please visit my English blog

Le vostre visite

I'm a VB!

Guarda la mia intervista a Seattle

Follow me on Twitter!

Altri spazi

GitHub
I miei progetti open-source su GitHub

Article Categories

Archives

Post Categories

Image Galleries

Privacy Policy

WPF: Introduzione al pattern Model-View-ViewModel per sviluppatori Visual Basic 2010 - quinta parte

Dopo aver fatto una pausa più o meno lunga sulla trattazione del pattern Model-View-ViewModel con WPF 4 e Visual Basic 2010, riprendiamo il discorso al punto in cui vi avevo promesso di continuare, ossia parlare di questo pattern nell’ambito di un contesto sicuramente più interessante come quello di adottarlo nei confronti di ADO.NET Entity Framework. Prima di addentrarci nel codice, sono opportune alcune illustrazioni discorsive.

Dove vogliamo arrivare

Alla fine della serie di post arriveremo a realizzare questo:

Quindi una semplice applicazione master-details, che permetterà di lanciare anche una finestra. Banale, indubbiamente, se si ragiona per gestori di eventi e click invece che per command binding, data binding e ViewModel. Ci renderemo infatti conto di come sia complesso questo pattern ma anche del perché ha i suoi benefici. Parleremo di message broker, di service locators e altro. Non ho l'obiettivo nè tanto meno la pretesa di illustrare un'applicazione reale. L'obiettivo che mi pongo, non solo come "relatore" ma come "studioso" del pattern, se volete, è quello di capirne i concetti, capire come funziona, capire il livello di astrazione da raggiungere. E' una precisazione che tenevo a fare.

Non sono esente da errori

Considerato quanto affermato al paragrafo precedente, non sono esente da errori. Ribadisco di non essere un guru di MVVM, semplicemente condivido con chi ha voglia di seguirmi quello che è il percorso che sto personalmente facendo nello studio del pattern. Per tale ragione non tutto potrebbe essere perfetto e ben vengano le correzioni e i feedback di chi, invece, padroneggia la materia.

Quante possibilità, quante "non regole"...

Già in precedenza abbiamo detto che MVVM è un pattern, non una regola. E' un insieme di linee guida che ha una finalità ben precisa, certo, ma ci sono tante, tantissime varianti e questo può portare a confusione. La verità è che nel momento in cui stavo per iniziare la serie di post dedicata a MVVM con Entity Framework, ho capito che non era poi così tutto scontato e studi ulteriori mi hanno permesso di capire quanto invece fosse il vero lavoro da fare per arrivare a un certo livello di astrazione, in questo aiutato dalle indicazioni di Corrado, Andrea, Raf, Davide, Karl Shifflett. Banalmente basterebbe far interagire il ViewModel con il DAL; funziona, senz'altro, ma non è "pulito". Ho scovato esempi su Internet in cui si faceva in questo modo, oppure esempi molto più complessi e "astrattivi" ma poco calzanti, a mio avviso. Mi sono convinto della bontà delle indicazioni ricevute dai "colleghi" MVP e su questa falsariga sono andato avanti. Il tutto, ripeto, può contenere degli errori che prontamente saranno fixati. Ora basta ciance, mettiamoci all'opera.

Creazione del progetto

Dopo aver avviato Microsoft Visual Studio 2010, creiamo un nuovo progetto WPF 4 con Visual Basic 2010. In questo primo post ci sarà poco di MVVM, prepareremo infatti la base di quanto ci servirà nei prossimi post. All'interno di Solution Explorer creiamo le seguenti folders: Models (che conterrà i nostri dati), ViewModels (per tutti i ViewModel), Views (per tutte le View), Commands (che conterrà le logiche di implementazione dei comandi), Helpers (che conterrà classi di supporto) e Services (che conterrà interfacce e classi di servizio che vedremo successivamente).

L'Entity Data Model, ovvero il nostro Model

ADO.NET Entity Framework è una tecnologia complessa per l'accesso ai dati. Non ci addentriamo in discorsi di tipo "domani model" et similia, semplicemente utilizzeremo Entity Framework come strato di accesso ai dati nei confronti del database dimostrativo Northwind, basato su SQL Server, del quale mapperemo come oggetti .NET tre tabelle tipiche: Customers, Orders e Order Details. Ciò premesso, tasto destro sul nome del progetto -> Add|New Item -> scheda Data e template ADO.NET Entity Data Model. Assegnamo all'EDM il nome Northwind.edmx e procediamo tramite il wizard selezionando il db Northwind e lasciando inalterate le impostazioni di default, accertandoci di selezionare le tabelle richieste come in figura:

Al termine della procedura guidata il designer di Entity Framework mostrerà in Visual Studio 2010 la rappresentazione grafica del mapping ottenuto, visibile in figura:

A questo punto abbiamo delle classi che rappresentano i nostri dati e per tale ragione possono essere considerate il nostro Model.

Considerazioni sulle Entities come Model

Come detto, le entità possono essere considerate come il nostro Model. Di default le entità implementano l'interfaccia INotifyPropertyChanged e questa è una ragione in più per considerarle come Model. In realtà, nei primi post su MVVM tale interfaccia è stata implementata nel ViewModel al fine di inviare notifiche alle View. Non è di certo un male se essa è implementata anche nel Model, fornendo un punto di notifica eventuale anche verso oggetti esterni come può essere per l'appunto un ViewModel.

Implementare la validazione dei dati

In questo post non parlerò di ViewModel. Piuttosto passeremo ad un qualcosa che precedentemente abbiamo visto solo nell'ultimo post, ossia prevedere delle regole di validazione. Facendo riferimento alla figura iniziale dell'applicazione che vogliamo realizzare, notiamo come sia possibile gestire gli ordini di ciascun cliente. Ipotizziamo che un ordine, nuovo o esistente, possa essere accettato solo qualora venga immesso lo Stato di destinazione, che nel nostro codice è rappresentato dalla proprietà ShipCountry della classe Order. Se avete un po' di dimestichezza con Entity Framework, sapete che il runtime genera due metodi utili allo scopo, OnXXXChanging e OnXXXChanged dove XXX è il nome della proprietà da validare. Nel nostro caso, avremo OnShipCountryChanging e OnShipCountryChanged. A noi interessa in modo particolare quest'ultimo, che viene richiamato quando la proprietà subisce una modifica. Poiché è buona regola di programmazione quella di non toccare i file autogenerati da Visual Studio, possiamo sfruttare le caratteristiche delle classi parziali e dei metodi parziali. In parole povere, creiamo una classe parziale Order che estenda quella autogenerata. Detto ciò, nella cartella Models del nostro progetto aggiungiamo una nuova classe e chiamiamo il file di codice come Order.vb. In questa classe:

  1. l'intestazione sarà di Partial Class

  2. implementeremo l'interfaccia IDataErrorInfo per la validazione dei dati

  3. implementeremo la logica di validazione

  4. all'interno del metodo OnShipCountryChanged invocheremo l'aggiunta o la rimozione di un errore alla collezione degli errori di validazione, a seconda della validità del valore immesso.

Il codice della classe è il seguente, opportunamente commentato:

Imports System.ComponentModel

 

Partial Public Class Order

    Implements IDataErrorInfo

 

    'Collezione di errore/descrizione

    Private m_validationErrors As New Dictionary(Of String, String)

 

    'Aggiunge un errore alla collezione, se non già presente

    'con la stessa chiave

    Private Sub AddError(ByVal columnName As String, ByVal msg As String)

        If Not m_validationErrors.ContainsKey(columnName) Then

            m_validationErrors.Add(columnName, msg)

        End If

    End Sub

 

    'Rimuove un errore dalla collezione, se presente

    Private Sub RemoveError(ByVal columnName As String)

        If m_validationErrors.ContainsKey(columnName) Then

            m_validationErrors.Remove(columnName)

        End If

    End Sub

 

    'Estende la classe con una proprietà che determina

    'se l'istanza ha errori di validazione

    Public ReadOnly Property HasErrors() As Boolean

        Get

            Return m_validationErrors.Count > 0

        End Get

    End Property

 

    'Restituisce un messaggio di errore

    'In questo caso è un messaggio generico, che viene

    'restituito se l'elenco di errori contiene elementi

    Public ReadOnly Property [Error] As String Implements System.ComponentModel.IDataErrorInfo.Error

        Get

            If m_validationErrors.Count > 0 Then

                Return "Order data is invalid"

            Else

                Return Nothing

            End If

        End Get

    End Property

 

 

    Default Public ReadOnly Property Item(ByVal columnName As String) As String _

                                     Implements System.ComponentModel.IDataErrorInfo.Item

        Get

            If m_validationErrors.ContainsKey(columnName) Then

                Return m_validationErrors(columnName).ToString

            Else

                Return Nothing

            End If

        End Get

    End Property

 

    'Metodo parziale che esegue la validazione

    Private Sub OnShipCountryChanged()

        If String.IsNullOrEmpty(Me.ShipCountry) Then

            Me.AddError("ShipCountry", "You need to specify the ship country")

        Else

            Me.RemoveError("ShipCountry")

        End If

 

    End Sub

 

    Public Sub New()

        'Gestendo quest'evento possiamo notificare alla UI

        'che l'associazione con il Customer è cambiata

        AddHandler Me.CustomerReference.AssociationChanged, AddressOf Customer_AssociationChanged

 

        'Imposta un valore di default

        Me.ShipCountry = String.Empty

 

        'Aggiunge subito l'errore quando un nuovo ordine viene creato,

        'di modo che l'utente sia obbligato a fixare il problema

        Me.AddError("ShipCountry", "You need to specify the ship country")

 

    End Sub

 

    'Evento che viene generato nel caso in cui l'utente cambia

    'l'associazione tra ordine e cliente

    Private Sub Customer_AssociationChanged(ByVal sender As Object, _

                                        ByVal e As CollectionChangeEventArgs)

        If e.Action = CollectionChangeAction.Remove Then

            OnPropertyChanging("Customer")

        Else

            If e.Action = CollectionChangeAction.Add Then

                Me.RemoveError("Customer")

            End If

            OnPropertyChanged("Customer")

        End If

    End Sub

 

End Class

Di interessante rileviamo il gestore di evento Customer.AssociationChanged che ci permette di verificare che sia cambiata l'associazione tra ordine corrente e cliente.

Fine della prima parte

Per il momento ci fermiamo qui, potendo dire di aver completato l'aggiunta del Model. Nel prossimo post vedremo come implementare uno strato di servizi che consenta ai ViewModel di lavorare con i dati, pur non sapendo che sta interagendo con Entity Framework, al fine di favorire un adeguato livello di astrazione. Come sempre, vi invito a lasciare i vostri commenti/suggerimenti/critiche/segnalazioni di imprecisioni. Non rilascio codice da scaricare, lo farò al termine della serie di post con il download dell'applicazione completa, così vi costringo a seguirmi :-)

Alessandro

Print | posted on domenica 25 luglio 2010 19:57 | Filed Under [ Visual Basic Windows Presentation Foundation Visual Studio 2010 ]

Powered by:
Powered By Subtext Powered By ASP.NET