Suggérer une traduction
 
Suggestions d'autres utilisateurs :

progress indicator
Aucune autre suggestion.
MSDN Magazine > Home > Issues > 2009 > Mars >  Modèles Silverlight : modèle-vue-ViewModel dans...
Affichage du contenu :  côte à côteAffichage du contenu : côte à côte
Ce contenu traduit automatiquement peut être modifié par les membres de la communauté. Nous vous invitons à améliorer la traduction en cliquant sur le lien « Modifier » associé aux phrases ci-dessous.
Silverlight Patterns
Model-View-ViewModel In Silverlight 2 Apps
Shawn Wildermuth
Code download available from the MSDN Code Gallery
Browse the Code Online

This article discusses:
  • Silverlight 2 development
  • Model-View-ViewModel pattern
  • Views and the View Model
  • Concessions to Silverlight 2
This article uses the following technologies:
Silverlight 2, Visual Studio
Now that Silverlight 2 has been released, the number of applications built on top of it is growing, and with that comes some growing pains. The basic structure supported by the Silverlight 2 template implies a tight integration between the user interface (UI) and any data that you are working with. While this tight integration is useful for learning the technology, it becomes a hindrance to testing, refactoring, and maintenance. I will show you how to separate the UI from the data by using mature patterns for application design.

The Problem
The core of the problem is tight coupling, which is the result of mixing the layers of your application together. When one layer has intimate knowledge about how another layer does its job, then your application is tightly coupled. Take a simple data entry application that lets you query for homes for sale in a particular city. In a tightly coupled application, you could define the query to perform the search in a button handler in your user interface. As the schema changes or the semantics of the search change, both the data layer and the user interface layer have to be updated.
This presents a problem in code quality and complexity. Every time the data layer changes, you have to synchronize and test the application to be sure that the changes are not breaking changes. When everything is bound tightly together, any movement in one part of the application can cause rippling changes across the rest of the code. When you are creating something simple in Silverlight2, like a movie player or a menu widget, tightly coupling the application's components is not likely to be an issue. As the size of a project increases, however, you will feel the pain more and more.
The other part of the problem is unit testing. When an application is tightly coupled, you can only do functional (or user interface) testing of the application. Again, this is not an issue with a small project, but as a project grows in size and complexity, being able to test application layers separately becomes very important. Keep in mind that unit testing is not just about making sure that a unit works when you use it in a system but about making sure it continues to work in a system. Having unit tests for parts of a system adds assurance that as the system changes, problems are revealed earlier in the process rather than later (as would happen with functional testing). Regression testing (for example, running unit tests on a system on every build) then becomes crucial to ensuring that small changes that are added to a system are not going to cause cascading bugs.
Creating an application by defining different layers might appear to some developers to be a case of over-engineering. The fact is that whether you build with layers in mind or not, you are working on an n-tier platform and your application will have layers. But without formal planning, you will end up with either a very tightly coupled system (and the problems detailed previously) or an application full of spaghetti code that will be a maintenance headache.
It is easy to assume that building an application with separate layers or tiers requires a lot of infrastructure to make it work well, but in fact, simple separation of layers is straightforward to implement. (You can design more complex layering of an application by using inversion of control techniques, but that addresses a different problem than is discussed in this article.)

Application Layering in Silverlight 2
Silverlight 2 does not require you to invent something new to help you decide how to layer an application. There are some well-known patterns that you can use for your design.
A pattern that people hear a lot about right now is the Model-View-Controller (MVC) pattern. In the MVC pattern, the model is the data, the view is the user interface, and the controller is the programmatic interface between the view, the model, and the user input. This pattern, however, does not work well in declarative user interfaces like Windows Presentation Foundation (WPF) or Silverlight because the XAML that these technologies use can define some of the interface between the input and the view (because data binding, triggers, and states can be declared in XAML).
Model-View-Presenter (MVP) is another common pattern for layering applications. In the MVP pattern, the presenter is responsible for setting and managing state for a view. Like MVC, MVP does not quite fit the Silverlight 2 model because the XAML might contain declarative data binding, triggers, and state management. So where does that leave us?
Luckily for Silverlight 2, the WPF community has rallied behind a pattern called Model-View-ViewModel (MVVM). This pattern is an adaptation of the MVC and MVP patterns in which the view model provides a data model and behavior to the view but allows the view to declaratively bind to the view model. The view becomes a mix of XAML and C# (as Silverlight 2 controls), the model represents the data available to the application, and the view model prepares the model in order to bind it to the view.
The model is especially important because it wraps the access to the data, whether access is through a set of Web services, an ADO.NET Data Service, or some other form of data retrieval. The model is separated from the view model so that the view's data (the view model) can be tested in isolation from the actual data. Figure 1 shows an example of the MVVM pattern.
fig01.gif
Figure 1 Model-View-ViewModel Pattern

MVVM: A Walk-Through
To help you understand how to implement the MVVM pattern, let's walk through an example. This example does not necessarily represent how actual code would be used. It is simply designed to explain the pattern.
This example is composed of five separate projects in a single Visual Studio solution. (Although you do not need to create each of the layers as a separate project, it is often a good idea.) The example further separates the projects by placing them in client and server folders. In the Server folder are two projects: an ASP.NET Web Application (MVVMExample) that will host our Silverlight projects and services and a .NET Library project that contains the data model.
In the Client folder are three projects: a Silverlight project (MVVM.Client) for the main UI of our app, a Silverlight client library (MVVM.Client.Data) containing the model and view model as well as service references, and a Silverlight project (MVVM.Client.Tests) containing the unit tests. You can see the breakdown of these projects in Figure 2.
Figure 2 Project Layout
For this example, I used ASP.NET, Entity Framework, and an ADO.NET Data Service on the server. Essentially, I have a simple data model on the server that I expose through a REST-based service. Please see my September 2008 article on using ADO.NET Data Services in Silverlight 2, " Data Services: Create Data-Centric Web Applications with Silverlight 2 " for a further explanation of those details.

Creating the Model
To enable layering in our Silverlight app, we first need to define the model for the application data in the MVVM.Client.Data project. Part of defining the model is determining the types of entities you are going to work with inside an application. The types of entities depend on how the application will interact with the server data. For example, if you are using Web services, your entities are likely going to be data contract classes that are generated by creating a service reference to your Web service. Alternatively, you could use simple XML Elements if you are retrieving raw XML in your data access. Here , I use an ADO.NET Data Service, so when I create a service reference to the data service, a set of entities is created for me.
In this example, the service reference created three classes that we care about: Game, Supplier, and GameEntities (the context object to access the data service). The Game and Supplier classes are the actual entities that we use to interact with the view, and the GameEntities class is used internally to access the data service to retrieve data.
Before we can create the model, however, we need to create an interface for communication between the model and the view model. This interface typically includes any methods, properties, and events that are required to access data. This set of functionality is represented by an interface to allow it to be replaced by other implementations as necessary (testing, for example). The model interface in this example, shown here, is called 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;
}
The IGameCatalog interface contains methods to retrieve and save data. However, none of the operations return actual data. Instead they have corresponding events for success and failure. This behavior enables asynchronous execution to address the Silverlight 2 requirement for asynchronous network activity. While an asynchronous design in WPF is often recommended, this particular design works well in Silverlight 2 because Silverlight 2 requires asynchronicity.
To enable notification of the results to the caller of our interface, the example implements a GameLoadingEventArgs class that is used in the events to send the results of a request. This class exposes our entity type (Game) as an enumerable list of entities that contains the results the caller requested, as you can see in the following code.
public class GameLoadingEventArgs : EventArgs
{
  public IEnumerable<Game> Results { get; private set; }

  public GameLoadingEventArgs(IEnumerable<Game> results)
  {
    Results = results;
  }
}
Now that we have defined our interface, we can create the model class (GameCatalog) that implements the IGameCatalog interface. The GameCatalog class simply wraps the ADO.NET Data Service so that when a request for data comes in (GetGames or GetGamesByGenre), it executes the request and throws an event that contains the data (or an error, if one occurs). This code is meant to simplify access to the data without imparting any specific knowledge to the caller. The class includes an overloaded constructor to specify the URI of the service, but that is not always needed and could be implemented as a configuration element instead. Figure 3 shows the code for the GameCatalog class.
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;
    }
  }
}
Notice the ExecuteGameQuery method, which takes the ADO.NET Data Service query and executes it. This method executes the result asynchronously and returns the result to the caller.
Note that the model executes the query but simply fires events when it is complete. You might look at this and wonder why the model doesn't ensure that the events marshal the calls to the user interface thread in Silverlight 2. The reason is that Silverlight (like its other user interface brethren, such as Windows Forms and WPF) can only update the user interface from a main or UI thread. But if we do that marshaling in this code, it would tie our model to the user interface, which is exactly counter to our stated purpose (separating the concerns). If you assume that the data needs to be returned on the UI thread, you bind this class to user interface calls, but this is antithetical to why you use separate layers in an application.

Views and the View Model
It might seem obvious to create the view model to expose data directly to our view class(es). The problem with this approach is that the view model should only expose data that is directly needed by the view; therefore, you need to understand what the view needs. In many cases, you will be creating the view model and the view in parallel, refactoring the view model when the view has new requirements. Although the view model is exposing data to the view, the view is also interacting with the entity classes (indirectly because the model's entities are being passed to the view by the view model).
In this example, we have a simple design that is used to browse XBox 360 game data, as shown in Figure 4. This design implies that we need a list of Game entities from our model filtered by genre (selected via the drop-down list). To fulfill this requirement, the example needs a view model that exposes the following:
  • A data-bindable Game list for the currently selected genre.
  • A method to make the request for the genre selected.
  • An event that alerts the UI that the list of games has been updated (because our data requests will be asynchronous).
Figure 4 Example User Interface
Once our view model supports this set of requirements, it can be bound to the XAML directly, as shown in GameView.XAML (located in the MVVM.Client project). This binding is implemented by creating a new instance of the view model in the Resources of the view and then binding the main container (a Grid in this case) to the view model. This implies that the entire XAML file will be data bound based on the view model directly. Figure 5 shows the GameView.XAML code.
// GameView.XAML
<UserControl x:Class="MVVM.Client.Views.GameView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://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>
Our view model needs to fulfill these requirements using an IGameCatalog interface. In general, its useful to have the default constructor of a view model create a default model so that binding to the XAML is easy, but you should also include an overload of the constructor in which the model is supplied to allow for scenarios such as testing. The example view model (GameViewModel) looks like Figure 6.
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;
    }
  }
}
Of particular interest in the view model are the handlers for GameLoadingComplete (and GameLoadingError). These handlers receive events from the model and then fire events to the view. What is interesting here is that the model passes the view model the list of results, but instead of passing the results directly to the underlying view, the view model stores the results in their own bindable list (ObservableCollection<Game>).
This behavior occurs because the view model is being bound directly to the view, so the results will show up in the view via the data binding. Because the view model has knowledge of the user interface (because its purpose is to fulfill UI requests), it can then make sure that the events that it fires happen on the UI thread (via Dispatcher.BeginInvoke, although you can use other methods for calling on the UI thread if you prefer).

Concessions to Silverlight 2
The MVVM pattern is used throughout many WPF projects to great success. The problem with using it in Silverlight 2 is that to make this pattern easy and seamless, Silverlight 2 really needs to support Commands and Triggers. If that were the case, we could have the XAML call the methods of the view model directly when the user interacts with the application.
In Silverlight 2 this behavior requires a bit more work, but luckily it involves writing only a little code. For example, when the user selects a different genre using the drop-down list, we would like to have a Command that executes the GameViewModel.GetGameByGenre method for us. Because the infrastructure required is not available, we simply have to use code to do the same thing. When the combo box's (genreComboBox) selection changes, the example loads the Games from the view model manually in code instead of in a Command. All that is required here is that the request to load the data happens—because we are bound to the list of Games, the underlying view model will simply change the collection we are bound to and the updated data appears automatically. You can see this code in Figure 7.
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);
  }

}
There are several places where the lack of element binding and commands will force Silverlight 2 developers to handle this behavior in code. Because the code is part of the view, this does not break the layering of the application, it is just not as straightforward as the all-XAML examples you will see in WPF.

Where We Are
Silverlight 2 does not require you to build monolithic applications. Layering Silverlight 2 applications is straightforward using the Model-View-ViewModel pattern that we happily borrow from our WPF brethren. Furthermore, using this layering approach allows you to loosely couple the responsibilities in your applications so that they are easier to maintain, extend, test, and deploy.
I'd like to thank the Laurent Bugnion (author of Silverlight 2 Unleashed) as well as others on the WPF Disciples mailing list for their help in completing this article. Laurent blogs at blog.galasoft.ch .

Shawn Wildermuth is a Microsoft MVP (C#) and the founder of Wildermuth Consulting Services. He is the author of several books and numerous articles. In addition, Shawn currently runs the Silverlight Tour teaching Silverlight 2 around the country. He can be contacted at shawn@wildermuthconsulting.com .

Modèles de Silverlight
Modèle View-ViewModel dans les applications Silverlight 2
Shawn Wildermuth
Téléchargement de code disponible de la bibliothèque de code MSDN
Parcourir le code en ligne

Cet article présente :
  • Développement Silverlight 2
  • Schéma modèle-vue-ViewModel
  • Affichages et le modèle d'affichage
  • Concessions pour Silverlight 2
Cet article utilise les technologies suivantes :
Silverlight 2, Visual Studio
Maintenant que Silverlight 2 a été publié, le nombre d'applications au-dessus d'elle est croissance et qui entraîne certaines efforce growing. La structure de base pris en charge par le modèle Silverlight 2 implique une intégration étroite entre l'interface utilisateur (IU) et les données que vous travaillez. Cette intégration étroite est utile pour la technologie de formation, il devient un obstacle test, la refactorisation et maintenance. Je vous montrerai comment séparer l'interface utilisateur à partir des données à l'aide de modèles à de conception d'application.

Le problème
Le cœur du problème est couplage, qui est le résultat de mixage ensemble les couches de votre application. Lorsqu'une couche a connaissance intimate comment une autre couche ne son travail, puis votre application est étroitement couplée. Prenez une application de saisie de données simples qui vous permet de vous interrogez de maisons à la vente dans une ville en particulier. Dans une application étroitement couplée, vous pouvez définir la requête pour effectuer la recherche dans un gestionnaire de bouton dans l'interface utilisateur. À mesure que les modifications de schéma ou la sémantique de la recherche modifiez, la couche de données et la couche d'interface utilisateur doivent être mis à jour.
Cela pose un problème dans code qualité et la complexité. Chaque fois que les données de couche modifications, vous devez synchroniser et tester l'application pour vous assurer que les modifications sont critiques pas de modifications. Lorsque tout est lié étroitement ensemble, tout déplacement dans une partie de l'application peut entraîner des modifications rippling dans le reste du code. Lorsque vous créez un élément simple dans Silverlight2, comme un lecteur vidéo ou un gadget de menu, étroitement associant les composants de l'application n'est probablement un problème pas. Que la taille d'un projet augmente, cependant, vous pensez plus en plus la difficulté.
L'autre partie du problème est test d'unité. Lorsqu'une application est étroitement couplée, vous pouvez uniquement faire fonctionnelle (ou interface utilisateur) de test de l'application. Là encore, cela n'est pas un problème avec un petit projet, mais comme un projet augmente en taille et complexité, pouvoir tester séparément les couches d'application est très important. Gardez à l'esprit cette unité de test n'est pas simplement assurant qu'une unité fonctionne lorsque vous l'utiliser dans un système mais sur l'exécution qu'il continue de fonctionner dans un système. Des tests d'unité pour les parties d'un système ajoute assurance que lorsque le modifié système, des problèmes sont revealed précédemment dans le processus plutôt qu'ultérieur (comme il se passerait avec des tests fonctionnels). Régression test (par exemple, l'exécution de tests d'unités sur un système sur chaque version) puis devient crucial pour s'assurer que petites modifications qui sont ajoutées à un système non allez entraîner des bogues en cascade.
Création d'une application en définissant différentes couches peut sembler certains développeurs à un cas d'over-Engineering. Le fait est que si vous créez avec des couches ESPRIT ou non, vous travaillez sur une plate-forme à n niveaux et votre application aura couches. Mais vous sans planification formelle, vous retrouvez avec soit un système très étroitement couplée (et les problèmes détaillée précédemment) ou une application complète de code méli-mélo qui sera un calvaire maintenance.
Il est facile de supposer que créer une application avec des calques distincts ou niveaux nécessite beaucoup d'infrastructure pour qu'il fonctionne correctement, mais en fait, simple séparation des couches est simple à implémenter. (Vous pouvez concevoir des couches plus complexes d'une application en utilisant inversion de contrôle techniques, mais qui corrige un problème différent qu'abordées dans cet article.)

Application superposition dans Silverlight 2
Silverlight 2 ne nécessite pas à inventer quelque chose de nouveau pour pouvoir décider comment à une application. Il existe certains modèles connus qui vous pouvez utiliser pour votre conception.
Un modèle personnes entendre un lot sur la droite maintenant est le modèle MVC (Modèle-Vue-Contrôleur). Dans le modèle MVC, le modèle est les données, l'affichage est l'interface utilisateur et le contrôleur est l'interface programmation entre la vue, le modèle et l'intervention de l'utilisateur. Ce modèle, toutefois, ne fonctionne pas correctement dans interfaces utilisateur déclaratives telles que Windows Presentation Foundation (WPF) ou Silverlight car le code XAML qui utilisent ces technologies peut définir des interface entre la saisie et l'affichage (parce que la liaison de données, les déclencheurs et états peuvent être déclarés dans le code XAML).
Modèles Affichage présentateur (MVP) est un autre modèle commun pour les applications de superposition. Dans le motif MVP, le présenteur est chargé pour définir et gestion de l'état d'un affichage. Comme MVC, MVP ne correspond pas tout à fait au modèle Silverlight 2 car le XAML peut contenir la liaison de données déclaratives, déclencheurs et gestion de l'état. Ainsi, où qui laisse nous ?
Heureusement pour Silverlight 2, la communauté WPF a rallied derrière un modèle appelé modèle-vue-ViewModel MVVM. Ce modèle est une adaptation des modèles MVC et MVP dans lequel le modèle d'affichage fournit un modèle de données et le comportement pour l'affichage mais permet l'affichage de manière déclarative lier le modèle d'affichage. L'affichage devient un mélange de XAML et en c# (comme les contrôles de Silverlight 2), le modèle représente les données disponibles pour l'application et le modèle d'affichage prépare le modèle afin de lier à l'affichage.
Le modèle est particulièrement important car il encapsule l'accès aux données, si l'accès est à travers un ensemble de services Web, un service de données ADO.NET ou une autre forme de récupération des données. Le modèle est séparé du modèle d'affichage afin que de données l'affichage (le modèle affichage) peut être testées dans l'isolation des données réelles. la figure 1 illustre un exemple du motif MVVM.
fig01.gif
Figure 1 modèle modèle-vue-ViewModel

MVVM : un Walk-Through
Pour vous aider à comprendre comment implémenter le modèle MVVM, examinons un exemple. Cet exemple montre comment ne représente pas nécessairement la véritable code sera utilisé. Il est simplement conçu pour expliquer le modèle.
Cet exemple est composé de cinq projets séparés dans une seule solution Visual Studio. (Bien que vous ne souhaitez pas créer de chacune des couches comme un projet distinct, il est souvent judicieux.) L'exemple plus sépare les projets en les plaçant dans dossiers client et serveur. Dans le dossier Server sont deux projets : une application Web ASP.NET (MVVMExample) qui est ordinateur hôte nos projets Silverlight et services et un projet de bibliothèque .NET qui contient le modèle de données.
Dans le dossier client sont trois projets : un projet Silverlight (MVVM.Client) pour le siège interface utilisateur de notre application, une bibliothèque de client Silverlight (MVVM.Client.Data) contenant le modèle et afficher modéliser ainsi que des références de service, et un projet Silverlight (MVVM.Client.Tests) contenant l'unité de tests. Vous pouvez visualiser la répartition de ces projets dans la figure 2 .
La figure 2 mise en page Project
Pour cet exemple, J'AI utilisé ASP.NET, Entity Framework et un service de données ADO.NET sur le serveur. En fait, J'AI disposer un modèle de données simples sur le serveur qui j'exposent via un service en fonction de REST. Consultez mon article de septembre 2008 sur l'aide des services de données ADO.NET dans 2 de Silverlight » Services de données : créer des applications Web centrée sur les données avec Silverlight 2 « Pour une explication plue de ces détails.

Création du modèle
Pour activer la superposition dans notre application Silverlight, nous devons définir le modèle pour les données d'application dans le projet MVVM.Client.Data. Partie de la définition du modèle consiste à déterminer les types d'entités, que vous allez travailler dans une application. Les types d'entités dépendent comment l'application interagit avec les données du serveur. Par exemple, si vous utilisez des services Web, vos entités probablement vont être des classes de contrat de données qui sont générées par création d'une référence de service à votre service Web. Sinon, vous pouvez utiliser éléments XML simple si vous souhaitez récupérer les code XML brut votre accès aux données. Ici, j'utilise un service de données ADO.NET, donc lorsque je crée une référence de service au service de données, un ensemble d'entités est créée pour moi.
Dans cet exemple, la référence de service créé trois classes qui nous intéresse : jeu, fournisseur et GameEntities (l'objet contexte accéder au service de données). Les classes de jeu et fournisseur sont les entités réelles qui nous permet d'interagir avec la vue, et la classe GameEntities est utilisée en interne pour accéder au service données pour récupérer des données.
Toutefois, avant de nous créer le modèle, nous devons créer une interface pour la communication entre le modèle et le modèle d'affichage. Cette interface inclut généralement les méthodes, propriétés et événements qui sont requises pour accéder aux données. Ce jeu de fonctionnalités est représenté par une interface pour l'autoriser à remplacer par d'autres implémentations si nécessaire (test, par exemple). L'interface de modèle dans cet exemple, illustré ici, est appelé 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'interface IGameCatalog contient des méthodes pour récupérer et enregistrer des données. Toutefois, aucune des opérations renvoyer les données réelles. Au lieu de cela, ils ont les événements correspondants pour réussite et l'échec. Ce comportement permet l'exécution asynchrone traiter la demande de 2 Silverlight pour activité réseau asynchrone. Pendant une conception asynchrone dans WPF est souvent recommandée, cette conception particulière fonctionne bien dans Silverlight 2 car 2 Silverlight nécessite asynchronicity.
Pour activer la notification des résultats à l'appelant de notre interface, l'exemple implémente une classe GameLoadingEventArgs utilisé dans les événements pour envoyer les résultats d'une requête. Cette classe expose notre type d'entité (jeu) en tant que liste énumérable des entités contenant les résultats de l'appelant demandé, comme vous pouvez le voir dans le code suivant.
public class GameLoadingEventArgs : EventArgs
{
  public IEnumerable<Game> Results { get; private set; }

  public GameLoadingEventArgs(IEnumerable<Game> results)
  {
    Results = results;
  }
}
Maintenant que nous avons défini notre interface, nous pouvons créer la classe de modèle (GameCatalog) qui implémente l'interface IGameCatalog. La classe GameCatalog encapsule simplement le service de données ADO.NET lorsqu'une demande de données arrive (GetGames ou GetGamesByGenre), il exécute la requête, génère un événement qui contient les données (ou une erreur,) si un se produit. Ce code est conçue pour simplifier l'accès aux données sans imparting des connaissances spécifiques à l'appelant. La classe inclut un constructeur surchargé pour spécifier l'URI du service, mais qui n'est pas toujours nécessaire et pourrait être implémentée comme un élément de configuration à la place. figure 3 illustre le code de 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;
    }
  }
}
Notez la méthode ExecuteGameQuery, qui prend la requête de service de données ADO.NET et l'exécute. Cette méthode exécute le résultat de façon asynchrone et renvoie le résultat à l'appelant.
Notez que le modèle exécute la requête mais simplement déclenche des événements lorsqu'il est terminée. Vous pouvez consulter ce et vous demander pourquoi le modèle n'est pas garantir que les événements marshaling les appels vers le thread d'interface utilisateur dans Silverlight 2. La raison est que Silverlight (tels que ses autres utilisateur interface brethren, tels que Windows Forms et WPF) pouvez mettre uniquement à jour l'interface utilisateur à partir d'une principale ou le thread d'interface utilisateur. Mais si nous faites ce regroupement dans ce code, il serait lier notre modèle de l'interface utilisateur, qui est exactement le compteur à notre accomplissement du but établi (séparant les problèmes). Si vous supposer que les données doivent être renvoyé sur le thread d'interface utilisateur, vous liez cette classe aux appels d'interface utilisateur, mais il est antithetical de Pourquoi utiliser des calques distincts dans une application.

Affichages et le modèle d'affichage
Il peut sembler évidente pour créer le modèle de mode pour exposer les données directement à notre classes d'affichage. Le problème avec cette approche est que le modèle d'affichage doit exposer uniquement les données qui sont nécessaire directement par l'affichage ; par conséquent, vous devez comprendre la vue doit. Dans de nombreux cas, vous allez créer le modèle d'affichage et l'affichage en parallèle, refactorisation le modèle d'affichage lorsque l'affichage a nouvelles exigences. Bien que le modèle d'affichage est exposer des données à l'affichage, l'affichage est également interaction avec les classes d'entité (indirectement car entités le modèle sont en cours transmises l'affichage par le modèle d'affichage).
Dans cet exemple, nous avons une conception simple qui permet de parcourir les données de jeu XBox 360, comme illustré figure 4 . Cette conception implique que nous devons une liste des entités jeu notre modèle filtrés par genre (sélectionné par la liste déroulante). Pour répondre à cette demande, l'exemple nécessite un modèle de vue qui expose les opérations suivantes :
  • Peuvent faire l'objet de liaisons de données jeu liste pour le genre actuellement sélectionné.
  • Une méthode pour faire la demande pour le genre sélectionné.
  • Événement qui alerte l'interface utilisateur que la liste de jeux a été mise à jour (étant donné que notre requêtes de données sera asynchrones).
La figure 4 exemple User Interface
Une fois que notre modèle mode prend en charge cet ensemble de conditions, il peut être lié à du code XAML directement, comme illustré GameView.XAML (située dans le projet MVVM.Client). Cette liaison est implémentée par créer une nouvelle instance du modèle d'affichage dans les ressources de l'affichage et liaison puis le conteneur principal (une grille dans ce cas) au modèle d'affichage. Cela implique que le fichier XAML entier sera données lié aux basé directement sur le modèle Affichage. figure 5 illustre le code GameView.XAML.
// GameView.XAML
<UserControl x:Class="MVVM.Client.Views.GameView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://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>
Notre modèle d'affichage doit répondre à ces exigences utilisant une interface IGameCatalog. En général, sa utile d'avoir le constructeur par défaut d'un modèle d'affichage créer un modèle par défaut de sorte que la liaison à du code XAML est facile, mais vous devez également inclure une surcharge du constructeur dans lequel le modèle est fourni pour autoriser scénarios tels que le test. Le modèle d'affichage (GameViewModel) exemple ressemble à la figure 6 .
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;
    }
  }
}
Intérêt particulier dans le modèle d'affichage sont les gestionnaires pour GameLoadingComplete (et GameLoadingError). Ces gestionnaires de recevoir des événements du modèle et ensuite déclencher des événements à l'affichage. <game>Ce qui est intéressant ici est que le modèle transmet le modèle d'afficher la liste des résultats, mais au lieu de passer les résultats directement à l'affichage sous-jacent, le modèle d'affichage stocke les résultats dans leur propre liste pouvant être lié (ObservableCollection <jeu>).
Ce problème se produit car le modèle d'affichage est en cours lié directement à l'affichage, donc les résultats vous montrera les dans l'affichage via la liaison de données. Car le modèle d'affichage a connaissance de l'utilisateur d'interface (parce que son objectif est pour satisfaire les demandes d'interface utilisateur), il peut puis assurez-vous qu'il déclenche les événements se produisent sur le thread d'interface utilisateur (via Dispatcher.BeginInvoke, bien que vous pouvez utiliser les autres méthodes pour appeler sur le thread d'interface utilisateur Si vous préférez).

Concessions pour Silverlight 2
Le modèle MVVM est utilisé dans plusieurs projets WPF à la grande réussite. Le problème à l'utilisation dans Silverlight 2 est que pour rendre ce modèle simple et transparente, Silverlight 2 réellement doit prendre en charge les commandes et des déclencheurs. Si qui était le cas, il peut avoir le XAML appeler directement les méthodes du modèle d'affichage lorsque l'utilisateur interagit avec l'application.
Dans Silverlight 2 ceci nécessite un peu plus de travail, mais heureusement qu'il implique écrire du code uniquement un peu. Par exemple, lorsque l'utilisateur sélectionne un genre différent à la liste déroulante, nous aimerions avoir une commande qui exécute la méthode GameViewModel.GetGameByGenre pour nous. Étant donné que l'infrastructure requise n'est pas disponible, nous devons tout simplement le code permet de faire la même chose. Lorsque de la zone de liste modifiable (genreComboBox) sélection change, la charge exemple jeux à partir de la vue du modèle manuellement dans du code au lieu de dans une commande. Il est nécessaire ici est que la demande de charger les données qui se produit, car nous est liés à la liste des jeux, le modèle d'affichage sous-jacent est simplement changer la collection nous sont liés à et les données mises à jour s'affiche automatiquement. Vous pouvez visualiser ce code dans la figure 7 .
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);
  }

}
Il existe plusieurs emplacements où l'absence de liaison de l'élément et les commandes forcera Silverlight 2 aux développeurs de traiter ce problème dans le code. Car le code fait partie de la vue, cela ne fractionne pas les couches de l'application, il est simplement pas aussi simple que les exemples de tout-code XAML que vous verrez dans WPF.

Il est
Silverlight 2 ne nécessite pas créer des applications monolithiques. Superposition des applications Silverlight 2 est simple utilisant le modèle modèle-vue-ViewModel que nous Heureusement empruntez de notre brethren WPF. En outre, cette approche couches permet faiblement couplé les responsabilités définies dans vos applications afin qu'ils soient plus facile à gérer, étendre, tester et déployer.
J'aimerais remercier le Bugnion Laurent (auteur de Silverlight 2 Unleashed), ainsi que d'autres utilisateurs sur la liste de distribution Disciples de WPF pour leur aide en fin de cet article. Blogs de Laurent au blog.galasoft.ch .

Shawn Wildermuth est un MVP Microsoft (C#) et le fondateur de Wildermuth Consulting Services. Il est l'auteur de plusieurs livres et de nombreux articles. En outre, Shawn exécute actuellement la présentation de Silverlight enseigner Silverlight 2 autour du pays. Il peut être contacté à Shawn@wildermuthconsulting.com .

Page view tracker