Condividi tramite


Il presente articolo è stato tradotto automaticamente.

Modelli Silverlight

Model-View-ViewModel nelle applicazioni Silverlight 2

Shawn Wildermuth

Download codice disponibile dalla Raccolta di codice di MSDN
Selezionare il codice in linea

Nell'articolo viene descritto:

  • Sviluppo di Silverlight 2
  • Schema ViewModel di visualizzazione del modello
  • Visualizzazioni e il modello di visualizzazione
  • Concessions per Silverlight 2
In questo articolo vengono utilizzate le seguenti tecnologie:
Silverlight 2, Visual Studio

Contenuto

Il problema
Applicazione Layering in Silverlight 2
MVVM: Walk-Through
Creazione del modello
Visualizzazioni e il modello di visualizzazione
Concessions per Silverlight 2
In cui si sono

Ora che è stato rilasciato Silverlight 2, il numero di applicazioni create su di essa è diffuso e viene fornito con alcuni pains growing.La struttura di base supportata dal modello Silverlight 2 implica una stretta integrazione tra l'interfaccia utente (UI) e i dati si siano utilizzando.Mentre questa stretta integrazione è utile per la tecnologia di apprendimento, diventa un ostacolo a test, refactoring e la manutenzione.Verrà illustrato è farne i dati dell'interfaccia utente utilizzando modelli maturo per progettazione dell'applicazione.

Il problema

Il nucleo del problema è ravvicinato accoppiamento, che è il risultato della combinazione contemporaneamente i livelli dell'applicazione.Quando un livello è buona conoscenza di come un livello diverso è il processo e l'applicazione è strettamente ridotto.Richiedere un'applicazione di immissione dati semplice che consente di query per abitazioni è rappresentato per la vendita in una determinata città.In un'applicazione strettamente ridotto, è possibile definire la query per eseguire la ricerca in un gestore pulsante l'interfaccia utente.Modifica le modifiche dello schema o la semantica della ricerca, sia il livello di dati e il livello di interfaccia utente devono essere aggiornati.

Questo presenta un problema nella qualità del codice e la complessità.Ogni volta che i dati di livello le modifiche, è necessario sincronizzare e testare l'applicazione per essere certi che modifiche non sono importanti le modifiche.Quando tutto ciò che viene associato perfettamente tra di loro, qualsiasi spostamento in una parte dell'applicazione può causare modifiche rippling attraverso il resto del codice.Quando si crea un'operazione semplice in Silverlight2, ad esempio un lettore filmati o widget un menu, stretta associazione componenti dell'applicazione non è probabilmente un problema.Come la dimensione di un progetto aumenta, tuttavia, si verranno ritiene più i problemi.

L'altri parte del problema è unit test.Quando un'applicazione è strettamente ridotto, è possibile solo eseguire funzionalità (o interfaccia utente) test di applicazione.Anche in questo caso, questo non è un problema con un piccolo progetto, ma quando un progetto diventa troppo grande dimensioni e complessità, diventa molto importante poter verificare livelli applicativi separatamente.Tenere presente che unit test è non praticamente assicurandosi che un'unità funziona quando è utilizzato in un sistema ma informazioni su come rendere che continua a funzionare in un sistema.Con gli unit test per parti di un sistema aggiunge la garanzia che le modifiche di sistema, problemi sono revealed precedenti nel processo anziché successiva (come viene eseguita con il testing funzionale).Regressione test (ad esempio, in esecuzione di unit test in un sistema su ogni build), quindi diventa fondamentale per garantire che piccole modifiche che vengono aggiunti a un sistema non intende causare errori CSS.

Creazione di un'applicazione definendo diversi livelli potrebbero risultare alcuni sviluppatori da un caso di over-engineering.Il fatto è che se creare con livelli presente o non, si lavora su una piattaforma di livello n e l'applicazione disporrà livelli.Ma senza pianificazione formali, si terminerà alto con un sistema molto stretta ridotto (e i problemi dettagliate in precedenza) o un'applicazione completa di codice spaghetti che sarà un headache di manutenzione.

È facile si presuppone che la generazione di un'applicazione con livelli separati o livelli richiede numerose infrastruttura per renderlo di lavoro e, ma in effetti, semplice separazione dei livelli è semplice da implementare.(È possibile progettare livelli più complessi di un'applicazione utilizzando l'inversione delle tecniche di controllo, ma che risolve un problema diverso rispetto a illustrate in questo articolo).

Applicazione Layering in Silverlight 2

2 Silverlight non richiede di inventare qualcosa di nuovo per decidere come un'applicazione di livello.Sono disponibili alcuni modelli noti che è possibile utilizzare per la progettazione.

Un modello che persone ascoltare un lotto sulla destra ora è il motivo di controller di visualizzazione del modello MVC.Nel modello MVC il modello è nei dati, la visualizzazione è l'interfaccia utente e il controller è l'interfaccia tra la visualizzazione, il modello e l'input dell'utente a livello di programmazione.Questo modello, tuttavia, non funziona anche nelle interfacce utente dichiarativa come Windows Presentation Foundation (WPF) o Silverlight perché il codice XAML che utilizzano queste tecnologie può definire una parte l'interfaccia tra l'input e la visualizzazione (poiché associazione dati, i trigger e stati possono essere dichiarati in XAML).

Modello visualizzazione relatore +++ (MVP) è un altro motivo comune per le applicazioni di livelli.In modello MVP, il moderatore è responsabile dell'impostazione e gestione dello stato per una visualizzazione.Come MVC, MVP non abbastanza rientra il modello di Silverlight 2 poiché il codice XAML può contenere associazione dati dichiarativa, trigger e gestione dello stato.Pertanto, in cui che lasciare ci?

Fortunatamente per Silverlight 2, la comunità di WPF è rallied dietro un criterio denominato modello visualizzazione ViewModel MVVM.Questo modello è un adattamento dei modelli MVC e MVP in cui il modello di visualizzazione fornisce un modello di dati e il comportamento per la visualizzazione ma consente la visualizzazione da associare al modello di visualizzazione in modo dichiarativo.La visualizzazione diventa una combinazione di codice XAML e C# (controlli Silverlight 2), il modello rappresenta i dati disponibili per l'applicazione e il modello di visualizzazione consente di preparare il modello per associarlo alla visualizzazione.

Il modello è particolarmente importante poiché esegue il wrapping di accesso ai dati, se l'accesso tramite un insieme di servizi Web, un servizio di dati ADO.NET o alcuni altri tipi di recupero dei dati.Il modello è separato dal modello di visualizzazione in modo che i dati la visualizzare (il modello di visualizzazione possono sottoporre a test in isolamento dai dati effettivi.Nella figura 1 viene illustrato un esempio del motivo MVVM.

fig01.gif

Figura 1 modello ViewModel di visualizzazione del modello

MVVM: Walk-Through

Per comprendere come implementare il modello MVVM, seguito dettagliatamente un esempio.In questo esempio non rappresenta necessariamente codice effettivo come verrebbe utilizzato.Semplicemente è progettato per spiegare il motivo.

In questo esempio è costituito da cinque progetti separati in una singola soluzione di Visual Studio.(Sebbene non debba creare tutti i livelli come un progetto separato, risulta spesso buona norma.) L'esempio separa i progetti inserendole in cartelle di client e server.Nella cartella di server sono due progetti: un'applicazione Web ASP.NET (MVVMExample) verrà host nostro progetti di Silverlight, servizi e un progetto libreria di .NET che include il modello di dati.

Nella cartella client sono tre progetti: un progetto di Silverlight (MVVM.Client) per il principale interfaccia utente dell'applicazione, una libreria di client Silverlight (MVVM.Client.Data) che contiene il modello e la visualizzazione del modello, nonché i riferimenti di servizio e verifica di un progetto di Silverlight (MVVM.Client.Tests) contenente l'unità.È possibile visualizzare la scomposizione di questi progetti nella Figura 2 .

fig02.gif

Nella figura 2 layout di progetto

Per questo esempio utilizzato ASP.NET, Entity Framework e un servizio di dati ADO.NET sul server.Essenzialmente, è possibile disporre un modello di dati semplice sul server che espongono tramite un servizio di base REST.Vedere mio articolo di settembre 2008 sull'utilizzo dei servizi di dati ADO.NET in Silverlight 2 "Servizi di dati: creare applicazioni Web basati sui dati con Silverlight 2"per ulteriori informazioni tali dettagli.

Creazione del modello

Per abilitare livelli dell'applicazione Silverlight, è necessario innanzitutto definire il modello per i dati dell'applicazione nel progetto MVVM.Client.Data.Parte della definizione di modello di consiste nel determinare i tipi di entità che si intende lavorare all'interno di un'applicazione.I tipi di entità dipendono dalla modalità di interazione l'applicazione con i dati del server.Ad esempio, se si utilizzano servizi Web, le entità probabilmente verranno da classi contratto dati generati da creando un riferimento servizio al servizio Web.In alternativa, è possibile utilizzare gli elementi XML semplice se si recuperano le codice XML non elaborato per l'accesso ai dati.È possibile utilizzare un servizio di dati ADO.NET in modo che quando si crea per me viene creato un riferimento servizio il servizio di dati, un set di entità.

In questo esempio, il riferimento del servizio creato tre classi interessante: gioco, fornitore e GameEntities (l'oggetto contesto accedere al servizio di dati.Le classi partita e fornitori sono le entità effettive che è possibile utilizzare per interagire con la visualizzazione e la classe GameEntities viene utilizzata internamente per accedere al servizio di dati per recuperare i dati.

Prima è possibile creare il modello, tuttavia, è necessario creare un'interfaccia per la comunicazione tra il modello e il modello di visualizzazione.Questa interfaccia include in genere eventuali metodi, la proprietà e gli eventi che devono accedere ai dati.Questo insieme di funzionalità è rappresentato da un'interfaccia per consentire di essere sostituito da altre implementazioni necessità (verifica, ad esempio).L'interfaccia del modello in questo esempio, illustrato di seguito, viene chiamato IGameCatalog.

public interface IGameCatalog
{
  void GetGames();
  void GetGamesByGenre(string genre);
  void SaveChanges();

  event EventHandler<GameLoadingEventArgs> GameLoadingComplete;
  event EventHandler<GameCatalogErrorEventArgs> GameLoadingError;
  event EventHandler GameSavingComplete;
  event EventHandler<GameCatalogErrorEventArgs> GameSavingError;
}

L'interfaccia IGameCatalog contiene metodi per recuperare e salvare i dati. Tuttavia, nessuna delle operazioni restituisce i dati effettivi. È invece gli eventi corrispondenti per esito positivo e negativo. Questo comportamento consente l'esecuzione asincrona soddisfare il fabbisogno 2 Silverlight per attività di rete asincrona. Durante una progettazione asincrona in WPF è spesso consigliabile questa struttura particolare funziona anche in Silverlight 2 poiché Silverlight 2 richiede asynchronicity.

Per attivare la notifica dei risultati al chiamante di nostra interfaccia, l'esempio che implementa una classe GameLoadingEventArgs utilizzato negli eventi per inviare i risultati di una richiesta. Questa classe espone il tipo di entità (giochi) come elenco di tipo enumerabile che contiene i risultati delle entità il chiamante richiesto, come si vede nel codice riportato di seguito.

public class GameLoadingEventArgs : EventArgs
{
  public IEnumerable<Game> Results { get; private set; }

  public GameLoadingEventArgs(IEnumerable<Game> results)
  {
    Results = results;
  }
}

È stata definita l'interfaccia, è possibile creare la classe di modello (GameCatalog) che implementa l'interfaccia IGameCatalog. La classe GameCatalog esegue semplicemente il servizio di data ADO.NET il wrapping in modo che quando una richiesta per i dati (GetGames o GetGamesByGenre), esegue la richiesta e genera un evento che contiene i dati (o un errore) se si verifica uno. Questo codice è destinato a semplificare l'accesso ai dati senza imparting le conoscenze specifiche al chiamante. La classe include un costruttore di overload per specificare l'URI del servizio, ma non è sempre necessario e può essere implementata come un elemento di configurazione invece. Nella figura 3 viene visualizzato il codice per la classe GameCatalog.

Figura 3 la classe GameCatalog

public class GameCatalog : IGameCatalog
{
  Uri theServiceRoot;
  GamesEntities theEntities;
  const int MAX_RESULTS = 50;

  public GameCatalog() : this(new Uri("/Games.svc", UriKind.Relative))
  {
  }

  public GameCatalog(Uri serviceRoot)
  {
    theServiceRoot = serviceRoot;
  }

  public event EventHandler<GameLoadingEventArgs> GameLoadingComplete;
  public event EventHandler<GameCatalogErrorEventArgs> GameLoadingError;
  public event EventHandler GameSavingComplete;
  public event EventHandler<GameCatalogErrorEventArgs> GameSavingError;

  public void GetGames()
  {
    // Get all the games ordered by release date
    var qry = (from g in Entities.Games
               orderby g.ReleaseDate descending
               select g).Take(MAX_RESULTS) as DataServiceQuery<Game>;

    ExecuteGameQuery(qry);
  }

  public void GetGamesByGenre(string genre)
  {
    // Get all the games ordered by release date
    var qry = (from g in Entities.Games
               where g.Genre.ToLower() == genre.ToLower()
               orderby g.ReleaseDate
               select g).Take(MAX_RESULTS) as DataServiceQuery<Game>;

    ExecuteGameQuery(qry);
  }

  public void SaveChanges()
  {
    // Save Not Yet Implemented
    throw new NotImplementedException();
  }

  // Call the query asynchronously and add the results to the collection
  void ExecuteGameQuery(DataServiceQuery<Game> qry)
  {
    // Execute the query
    qry.BeginExecute(new AsyncCallback(a =>
    {
      try
      {
        IEnumerable<Game> results = qry.EndExecute(a);

        if (GameLoadingComplete != null)
        {
          GameLoadingComplete(this, new GameLoadingEventArgs(results));
        }
      }
      catch (Exception ex)
      {
        if (GameLoadingError != null)
        {
          GameLoadingError(this, new GameCatalogErrorEventArgs(ex));
        }
      }

    }), null);
  }

  GamesEntities Entities
  {
    get
    {
      if (theEntities == null)
      {
        theEntities = new GamesEntities(theServiceRoot);
      }
      return theEntities;
    }
  }
}

Si noti il metodo ExecuteGameQuery che accetta la query del servizio dati ADO.NET e viene eseguito. Questo metodo viene eseguito il risultato in modo asincrono e restituisce il risultato al chiamante.

Si noti che il modello esegue la query, ma semplicemente genera eventi al termine. È possibile esaminare questo e domandarsi perché il modello non verificare gli eventi il marshalling delle chiamate al thread dell'interfaccia utente in Silverlight 2. Il motivo è che Silverlight (come l'altri utente interfaccia brethren, ad esempio Windows Form e WPF) aggiornarne solo l'interfaccia di utente di un principale o il thread dell'interfaccia utente. Ma se si esegue il marshalling nel codice, è necessario collegare il modello per l'interfaccia utente, che è esattamente contatore a nostro scopo dichiarato (separare i problemi). Se si suppone che i dati devono essere restituiti in thread dell'interfaccia utente, si associa questa classe a chiamate di interfaccia utente, ma è antithetical perché è possibile utilizzare livelli separati in un'applicazione.

Visualizzazioni e il modello di visualizzazione

Potrebbe sembrare ovvio per creare il modello di visualizzazione per esporre i dati direttamente a nostro classi di visualizzazione. Il problema con questo approccio è che il modello di visualizzazione deve esporre solo dati che sono necessario direttamente dalla vista; di conseguenza, è necessario capire cosa è la visualizzazione. In molti casi, si verrà da creando il modello di visualizzazione e la visualizzazione in parallelo, refactoring il modello di visualizzazione quando la visualizzazione è nuovi requisiti. Anche se il modello di visualizzazione è esporre dati per la visualizzazione, la visualizzazione anche interagisce con le classi di entità (indirettamente quanto entità del modello sono passata alla visualizzazione per il modello di visualizzazione).

In questo esempio abbiamo una struttura semplice che viene utilizzata per visualizzare i dati di giochi XBox 360, come illustrato nella Figura 4 . Questa struttura implica che è necessario un elenco di entità partita dal nostro modello filtrato in base a genere (selezionato tramite l'elenco a discesa. Per soddisfare questo requisito, l'esempio è necessario un modello di visualizzazione che espone le operazioni seguenti:

  • Un elenco di gioco associabile a dati per il genere selezionato.
  • Un metodo per effettuare la richiesta per genere selezionato.
  • Un evento che segnala l'interfaccia utente che l'elenco dei giochi è stato aggiornato (poiché il richieste di dati sarà asincrone).

fig04.gif

Nella figura 4 dell'interfaccia utente di esempio

Una volta il modello di visualizzazione supporta questo insieme di requisiti, può essere associato per il codice XAML direttamente, come illustrato nella GameView.XAML (nella cartella del progetto MVVM.Client). Questa associazione è implementata da creare una nuova istanza del modello di visualizzazione di risorse della visualizzazione e quindi associare il contenitore principale (una griglia in questo caso a modello di visualizzazione. Ciò implica che l'intero file XAML sarà dati con associazione a basato sul modello di visualizzazione direttamente. Nella figura 5 viene visualizzato il codice GameView.XAML.

Nella figura 5 GameView.XAML

// GameView.XAML
<UserControl x:Class="MVVM.Client.Views.GameView"
             xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:data="clr-namespace:MVVM.Client.Data;assembly=MVVM.Client.Data">

  <UserControl.Resources>
    <data:GamesViewModel x:Key="TheViewModel" />
  </UserControl.Resources>

  <Grid x:Name="LayoutRoot"
        DataContext="{Binding Path=Games, Source={StaticResource TheViewModel}}">
    ...
  </Grid>
</UserControl>

Il modello di visualizzazione è necessario soddisfare questi requisiti utilizzando un'interfaccia IGameCatalog. In generale, relativa utile il costruttore predefinito di un modello di visualizzazione creare un modello predefinito in modo che associazione del codice XAML è semplice, ma è necessario includere anche un overload del costruttore in cui il modello viene fornito per consentire scenari quali la verifica. Modello di visualizzazione (GameViewModel) di esempio è simile a figura 6 .

Nella figura 6 GameViewModel classe

public class GamesViewModel
{
  IGameCatalog theCatalog;
  ObservableCollection<Game> theGames = new ObservableCollection<Game>();

  public event EventHandler LoadComplete;
  public event EventHandler ErrorLoading;

 public GamesViewModel() : 
    this(new GameCatalog())
  {
  }

  public GamesViewModel(IGameCatalog catalog)
  {
    theCatalog = catalog;
    theCatalog.GameLoadingComplete += 
      new EventHandler<GameLoadingEventArgs>(games_GameLoadingComplete);
    theCatalog.GameLoadingError += 
      new EventHandler<GameCatalogErrorEventArgs>(games_GameLoadingError);
  }

  void games_GameLoadingError(object sender, GameCatalogErrorEventArgs e)
  {
    // Fire Event on UI Thread
    Application.Current.RootVisual.Dispatcher.BeginInvoke(() =>
      {
        if (ErrorLoading != null) ErrorLoading(this, null);
      });
  }

  void games_GameLoadingComplete(object sender, GameLoadingEventArgs e)
  {
    // Fire Event on UI Thread
    Application.Current.RootVisual.Dispatcher.BeginInvoke(() =>
      {
        // Clear the list
        theGames.Clear();

        // Add the new games
        foreach (Game g in e.Results) theGames.Add(g);

        if (LoadComplete != null) LoadComplete(this, null);
      });
  }

  public void LoadGames()
  {
    theCatalog.GetGames();
  }

  public void LoadGamesByGenre(string genre)
  {
    theCatalog.GetGamesByGenre(genre);
  }

  public ObservableCollection<Game> Games
  {
    get
    {
      return theGames;
    }
  }
}

Di particolare interesse nel modello di visualizzazione sono i gestori per GameLoadingComplete e GameLoadingError. Questi gestori ricevere gli eventi dal modello e quindi generare eventi per la visualizzazione. Che cos'è interessante è qui che il modello passa il modello di visualizzazione nell'elenco dei risultati, ma invece di passare i risultati direttamente alla visualizzazione sottostante, il modello di visualizzazione vengono memorizzati i risultati nel proprio elenco associabile (ObservableCollection <game>).

Questo comportamento si verifica perché il modello di visualizzazione viene associato direttamente alla visualizzazione, in modo che i risultati verranno visualizzate nella visualizzazione mediante l'associazione dati. Perché il modello di visualizzazione è conoscenza dell'interfaccia utente, poiché il suo scopo è per soddisfare le richieste dell'interfaccia utente, è possibile quindi assicurarsi che gli eventi che viene generato eseguita sul thread UI (tramite Dispatcher.BeginInvoke, sebbene sia possibile utilizzare altri metodi per la chiamata sul thread UI se si preferisce).

Concessions per Silverlight 2

Il modello MVVM viene utilizzato in molti progetti WPF per grande successo. Il problema utilizzo in Silverlight 2 è che per rendere questo modello semplice e trasparente, Silverlight 2 effettivamente deve supportano comandi e trigger. Se che fosse la distinzione tra maiuscole e minuscole, è possibile che il codice XAML chiamare direttamente i metodi del modello di visualizzazione quando l'utente interagisce con l'applicazione.

In Silverlight 2 questo comportamento richiede un'ulteriore lavoro, ma Fortunatamente comporta la scrittura di solo un codice. Ad esempio, quando l'utente seleziona un genere diverso utilizzando l'elenco a discesa, desideriamo dispone di un comando che esegue il metodo GameViewModel.GetGameByGenre per noi. Poiché l'infrastruttura necessaria non è disponibile, è semplicemente necessario utilizzare il codice per eseguire la stessa operazione. Quando della casella combinata (genreComboBox) selezione viene modificata, il caricamento di esempio giochi dalla visualizzazione modello manualmente nel codice anziché in un comando. È necessario qui è che si verifica la richiesta di caricare i dati, poiché è sono associati all'elenco dei giochi, il modello di visualizzazione sottostante verrà semplicemente modificare l'insieme associati a e i dati aggiornati verranno visualizzati automaticamente. È possibile visualizzare il codice illustrato nella Figura 7 .

Nella figura 7 aggiornamento dei dati nell'interfaccia utente

void genreComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
  string item = genreComboBox.SelectedItem as string;
  if (item != null)
  {
    LoadGames(item);
  }
}

void LoadGames(string genre)
{
  loadingBar.Visibility = Visibility.Visible;
  if (genre == "(All)")
  {
    viewModel.LoadGames();
  }
  else
  {
    viewModel.LoadGamesByGenre(genre);
  }

}

Esistono diverse posizioni in cui la mancanza di associazione dell'elemento e i comandi forzerà agli sviluppatori di Silverlight 2 di gestire questo comportamento nel codice. Poiché il codice fa parte della visualizzazione, questo non interrotta la sovrapposizione dell'applicazione, ma non è come semplice come l'esempi di XAML tutti che si vedrà in WPF.

In cui si sono

2 Silverlight non richiede di creare applicazioni monolitica. Layering applicazioni Silverlight 2 è semplice utilizzando il criterio ViewModel di visualizzazione del modello che si richiede Fortunatamente dal nostro brethren WPF. Inoltre, utilizzando questo approccio livelli consente ad accoppiamento associa le responsabilità nelle applicazioni in modo che siano la manutenzione, estendere, test e distribuire.

Vorrei ringraziare il Bugnion Laurent (autore di Silverlight 2 Unleashed), nonché ad altri utenti nell'elenco di indirizzi Disciples WPF per le informazioni di completamento di questo articolo. Blog Laurent inblog.galasoft.CH.

Shawn Wildermuth è un MVP di Microsoft (C#) e fondatore di servizi di consulenza Wildermuth. È autore di numerosi libri e articoli diversi. Inoltre, Shawn è in esecuzione la presentazione di Silverlight insegnamento 2 Silverlight intorno al paese. È possibile contattare inShawn@wildermuthconsulting.com.