Guide de programmation MEF


précédentsommairesuivant

III. Fonctionnalités spécifiques à Silverlight

III-A. Héberger MEF dans Silverlight en utilisant le CompositionInitializer

Dans une application de bureau, vous devez configurer manuellement le CompositionContainer ainsi que les catalogues afin que l'application puisse découvrir les Parts. Ce container a souvent besoin d'être exposé (de préférence à travers un wrapper) et d'être utilisé par les Parts qui en ont besoin afin de composer dynamiquement de nouvelles Parts.

Dans Silverlight, une nouvelle API est introduite. Appelée System.ComponentModel.Composition.CompositionInitializer, elle est présente dans l'assemblage System.ComponentModel.CompositionInitialization. La classe CompositionInitializer permet aux Parts d'être composées par MEF sans avoir à la faire à la main car elle va automatiquement configurer MEF à la demande. Avec CompositionInitializer, n'importe quelle classe nouvellement instanciée peut avoir des imports, MEF les satisfera. Cela signifie que vous pouvez l'utiliser n'importe où dans votre application Silverlight. Néanmoins, une bonne pratique est de l'utiliser dans la classe App afin d'importer le MainView, ou le MainViewModel si vous utilisez le pattern MVVM comme dans l'exemple ci-dessous :

 
Sélectionnez
using System.ComponentModel.Composition;

public class App : Application {
  public App() {
    this.Startup += this.Application_Startup;
    this.Exit += this.Application_Exit;
    this.UnhandledException += this.Application_UnhandledException;

    InitializeComponent();
  }

  private void Application_Startup(object sender, StartupEventArgs e)
  {
    CompositionInitializer.SatisfyImports(this)
    var mainPage = new MainPage();
    mainPage.DataContext = ViewModel;
    RootVisual = mainPage;
  }

  [Import]
  public MainViewModel ViewModel {get;set;}
}

[Export]
public class MainViewModel : INotifyPropertyChanged {
  [Import]
  public ILogger Loger {get;set;}
}

[Export(typeof(ILogger))]
public class Logger : ILogger {
}

App importe un MainViewModel. Puis au démarrage de l'application, elle appelle CompositionInitializer en se passant elle-même en paramètre afin de satisfaire ses imports. MainViewModel est donc découvert par MEF qui va lui injecter une instance de Logger. L'application instancie ensuite une MainView et lui affecte comme DataContext le ViewModel importé.

Que se passe-t-il réellement ?

La première fois que la méthode SatisfyImports est appelée, le CompositionInitializer crée un nouveau container partagé qui sera utilisé pour les futurs appels à SatisfyImports. Le catalogue de ce container contient toutes les Parts découvertes dans les assemblages du XAP principal de l'application. Cela signifie qu'en plus de l'assemblage exécuté, tous les assemblages référencés (et leurs références) ont leurs Parts découvertes.

La classe Application n'a pas d'attribut Export. La méthode SatisfyImports est faite pour utiliser seulement les Parts ne pouvant être découvertes via le catalogue, c'est à dire les Parts n'ayant pas d'exports. Si vous tentez de passer en paramètre de SatisfyImports une instance ayant des exports, cela lèvera une exception.

III-A-1. Utilisation de CompositionInitializer depuis des éléments XAML

La classe CompositionInitializer peut être appelée plusieurs fois avec la configuration créée lors de son premier appel. Cela la rend idéale pour l'utiliser non seulement dans la classe Application, mais aussi dans les éléments créés en XAML. Par exemple, regardez le code ci-dessous :

 
Sélectionnez
<UserControl
 x:Class="OrderManagement.OrderView"
 xmlns:c="clr-namespace:MyApp.Controls">
  <StackPanel>
    <OrderHeader/>
    <OrderDetails/>
  </StackPanel>
<UserControl>

OrderHeader et OrderDetails sont des contrôles imbriqués dans OrderView et sont créés en XAML. Cependant les deux ont leur ViewModel respectif qu'ils importent. Le code ci-dessous représente OrderHeader. Notez qu'il ne possède pas d'attribut d'export.

 
Sélectionnez
public class OrderHeader : UserControl {
  [Import]
  public OrderHeaderViewModel ViewModel {get;set;}

  public OrderHeader() {
    CompositionInitializer.SatisfyImports(this);
    this.DataContext = ViewModel;
  }
}

Dans cet exemple, OrderHeader importe directement son ViewModel plutôt que d'être lié par un élément externe comme dans le cas du MainView étudié plus haut. L'import direct permet à un élément d'être utilisé en XAML sans avoir besoin de connaître son lien avec le contrôle conteneur. Ce type de pattern prend tout son sens pour les contrôles tiers utilisant MEF pour simplifier leur utilisation. Après, c'est une question de préférence.

III-A-2. Mises en garde dans l'utilisation de CompositionInitializer.SatisfyImports

  • Par défaut seuls les assemblages dans le XAP courant sont découverts. Vous pouvez cependant redéfinir ce comportement en utilisant la configuration de l'hôte (vu dans le chapitre suivant) ;
  • toutes les Parts créées avec le CompositionInitializer sont maintenues par MEF jusqu'à la fin de l'exécution de l'application. Ce n'est donc pas l'idéal pour la composition de Parts multi-instances. Dans ce cas, on peut utiliser l'ExportFactory à la place du CompositionInitializer (vu dans les chapitres suivants) ;
  • comme mentionné plus haut, les exports ne sont pas supportés.

III-B. Redéfinir la configuration de l'hôte

MEF crée une configuration par défaut de l'hôte pour le CompositionInitializer la première fois que SatisfyImports est appelée. C'est idéal pour débuter avec MEF ou pour une application simple qui va seulement découvrir les Parts de son XAP. Pour des scénarios plus complexes, comme le partitionnement d'application (vu dans les chapitres suivants), la classe System.ComponentModel.Composition.Hosting.CompositionHost (dans l'assemblage System.ComponentModel.Composition.Initialization.dll) permet de redéfinir la configuration. Pour utiliser CompositionHost, il faut appeler sa méthode d'initialisation au démarrage de l'application et lui spécifier la configuration que MEF utilisera.

CompositionHost.Initialize ne peut être appelée qu'une seule fois. Tous les appels suivants lèveront une exception. Il est aussi recommandé que seul l'hôte appelle cette méthode, plutôt qu'un composant tiers.

III-B-1. Redéfinition grâce aux catalogues

La façon la plus simple de redéfinir la configuration est d'appeler la surcharge de Initialize qui prend en paramètre des catalogues. MEF va créer un container qui utilisera ces catalogues.

Dans le code ci-dessous, la configuration est redéfinie via un AggregateCatalog permettant d'ajouter d'autres catalogues dans le futur.

 
Sélectionnez
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;

public class App : Application {
  public App() {
    this.Startup += this.Application_Startup;
    this.Exit += this.Application_Exit;
    this.UnhandledException += this.Application_UnhandledException;

    InitializeComponent();
  }

  private void Application_Startup(object sender, StartupEventArgs e)
  {
    var aggregateCatalog = new AggregateCatalog();
    var assemblyCatalog = new AssemblyCatalog(typeof(App).Assembly);
    CompositionHost.Initialize(assemblyCatalog, aggregateCatalog);
    CompositionInitializer.SatisfyImports(this);  //imports are satisfied
    ...
   }

   [Import]
   public MainViewModel ViewModel {get;set;}

L'application appelle Initialize en lui passant un AggregateCatalog et un AssemblyCatalog pour l'assemblage courant. Satisfyimports est ensuite appelée et va donc importer ViewModel.

III-B-1-1. Configurer la découverte des Parts dans le XAP courant

Quand vous redéfinissez la configuration vous prenez le contrôle total, MEF ne va plus assumer la configuration. Cela signifie que les Parts dans le XAP principal ne sont pas découvertes. Dans l'exemple du dessus, nous lui passons un catalogue d'assemblage avec l'assemblage courant. Si vous souhaitez que le XAP principal soit découvert, vous pouvez utiliser un DeploymentCatalog créé avec son constructeur par défaut. Cela va dire à MEF « trouve toutes les Parts dans le XAP courant ». Voilà un exemple d'utilisation de DeploymentCatalog :

 
Sélectionnez
  private void Application_Startup(object sender, StartupEventArgs e)
  {
    var aggregateCatalog = new AggregateCatalog();
    CompositionHost.Initialize(new DeploymentCatalog(),aggregateCatalog); //add the current XAP.
    CompositionInitializer.SatisfyImports(this);  //imports are satisfied
    ...
   }

III-B-2. Redéfinition grâce à un container

La redéfinition grâce aux catalogues suffit dans la plupart des cas. Mais pour des scénarions plus avancés, vous pouvez vouloir redéfinir le container lui-même. Pour faire ceci, il faut appeler la surcharge d'Initialize qui accepte un container. Voici un code d'exemple :

 
Sélectionnez
  private void Application_Startup(object sender, StartupEventArgs e)
  {
    var aggregateCatalog = new AggregateCatalog();
    var container = new CompositionContainer(new DeploymentCatalog(), aggregateCatalog);
    CompositionHost.Initialize(container);
    CompositionInitializer.SatisfyImports(this);  //imports are satisfied
    ...
   }

III-C. Instanciation dynamique et ExportFactory<T>

Un import différé fournit un importateur avec une seule instance du contrat d'export. La valeur sous-jacente est créée lors de la première utilisation, les requêtes suivantes retournant la même instance.

 
Sélectionnez
[Import]
Lazy<IFoo> foo;

// elsewhere in the same class

Assert.AreSame(foo.Value, foo.Value);

III-C-1. ExportFactory<T>

Parfois une Part a besoin de créer à la volée plusieurs instances non-partagées d'un export. Par exemple, une application de management de commandes à besoin de créer à la volée de nouveaux OrderViewModel quand une nouvelle commande est créée. Dans ces situations, ExportFactory<T> est plus appropriée que Lazy<T> :

 
Sélectionnez
[Export]
public class OrderController {

  [Import] 
  public ExportFactory<OrderViewModel> OrderVMFactory {get;set;}

  public OrderViewModel CreateOrder() {
    return OrderVMFactory.CreateExport().Value;
  }
}

La classe OrderController importe une factory chargée de créer des OrderViewModels. Chaque fois que l'application va appeler la méthode CreateOrder, le controller va utiliser la factory afin de créer une nouvelle instance de OrderViewModel. Cette instance est créée par le container sous-jacent qui fournit les imports.

L'Import d'un ExportFactory<T> est très similaire à l'import d'un Lazy<T>. Les mêmes règles de détermination du nom de contrat et du type s'appliquent, et le type ExportFactory<T, TMetadata> présente une métadonnée fortement typée dans le même genre que Lazy<T, TMetadata>.

III-C-2. ExportLifetimeContext<T>

La valeur de retour de la méthode ExportFactory<T>.CreateExport() est de type ExportLifetimeContext<T>. Ce type expose la valeur de l'export de la Part créée via la propriété Value. Il fournit également une implémentation de IDisposable, donc quand la valeur exportée n'est plus requise, la Part sous-jacente et toutes ses dépendances peuvent être nettoyées.

Plus de détails dans ce post de Nicolas Blumhardt.

III-D. Partitionnement d'application avec le DeploymentCatalog

Le modèle de programmation par défaut de Silverlight requiert que toutes les Parts MEF soient dans le XAP courant. Ce n'est pas un problème pour de simples applications composables, mais cela le devient pour des applications plus grosses :

  • le XAP par défaut va gonfler continuellement ce qui va entraîner le téléchargement de l'app initiale et gâcher l'expérience utilisateur lors du démarrage ;
  • ça ne permet pas une extensibilité comme celle offerte par des applications de bureau. Dans bien des scénarios il serait idéal de permettre à l'application de s'étendre sur le serveur ;
  • il est difficile d'ajouter des fonctionnalités à une grosse application quand plusieurs équipes travaillent dessus.

III-D-1. DeploymentCatalog

Le DeploymentCatalog télécharge les Parts depuis les XAPs présents sur le serveur web. Vous pouvez donc diviser votre application en autant de XAPs que vous souhaitez et utiliser un DeploymentCatalog afin de les remettre ensemble par la suite. Le DeploymentCatalog télécharge les catalogues de façon asynchrone et implémente le pattern d'événement async afin de vous permettre de suivre le téléchargement, à savoir traquer la fin du téléchargement, les échecs, etc. Il n'est pas requis de le faire, c'est néanmoins très recommandé.

Pour utiliser un DeploymentCatalog avec un CompositionInitializer, vous devez redéfinir la configuration en utilisant CompositionHost, sinon le CompositionInitializer ne verra pas les Parts téléchargées.

III-D-2. Télécharger des Parts après le démarrage de l'application

Le scénario le plus commun et le plus simple est de réduire l'empreinte de démarrage de votre application et de démarrer immédiatement le téléchargement des autres parties en arrière-plan. Par exemple ci-dessous (le codage des modules est à titre illustratif, il n'est pas obligatoire) l'application de management des commandes démarre avec seulement les modules Home et Order. Le téléchargement des modules Admin, Reporting et Forecasting qui ne sont pas requis initialement est alors immédiatement initié. Voici un diagramme explicitant le design de l'application :

image

Et le code montrant comment cela fonctionne :

 
Sélectionnez
public class App : Application {
  public App() {
    this.Startup += this.Application_Startup;
    this.Exit += this.Application_Exit;
    this.UnhandledException += this.Application_UnhandledException;

    InitializeComponent();
  }

  private void Application_Startup(object sender, StartupEventArgs e)
  {
    var catalog = new AggregateCatalog();
    catalog.Catalogs.Add(CreateCatalog("Modules/Admin"));
    catalog.Catalogs.Add(CreateCatalog("Modules/Reporting"));
    catalog.Catalogs.Add(CreateCatalog("Modules/Forecasting"));

    CompositionHost.Initialize(new DeploymentCatalog(), catalog);
    CompositionInitializer.SatisfyImports(this);

    RootVisual = new MainPage();
  }

  private DeploymentCatalog CreateCatalog(string uri) {
    var catalog = new DeploymentCatalog(uri);
    catalog.DownloadCompleted += (s,e) => DownloadCompleted(s, e);
    catalog.DownloadAsync();
    return catalog;
  }

  private void DownloadCompleted(object sender, AsyncCompletedEventArgs e) {
    if (e.Error != null) {
      MessageBox.Show(e.Error.Message);
    }
  }

  private Lazy<IModule, IModuleMetadata>[] _modules;

  [ImportMany(AllowRecomposition=true)]
  public Lazy<IModule, IModuleMetadata>[] Modules {
    get{return _modules;}
    set{
      _modules = value;
      ShowModules();
    }
  }

  private void ShowModules() {
    //logic to show the modules
  }

}

De nombreuses choses ont été réalisées afin de permettre le fonctionnement de cette application. Premièrement, un AggregateCatalog est créé, auquel on ajoute trois DeploymentCatalogs pour les modules en utilisant la méthode d'aide CreateCatalog. Cette dernière fonctionne comme ceci :

  • crée un nouveau DeploymentCatalog basé sur l'URI passée en paramètre ;
  • s'abonne à l'événement DownloadCompleted. Ceci sert principalement à traquer les erreurs de téléchargement ;
  • démarre le téléchargement asynchrone ;
  • retourne le catalogue.

Par la suite, la méthode CompositionHost.Initialize est appelée avec pour paramètres un DeploymentCatalog vide (afin de récupérer les Parts du XAP courant) et l'AggregateCatalog contenant les DeploymentCatalogs qui seront téléchargés. Et enfin l'application compose. Elle n'a qu'un import pour tous les modules disponibles, ce qui au démarrage correspondra aux modules inclus dans le XAP. Une fois les modules importés, la méthode ShowModules est appelée afin de les afficher. Notez que l'application n'attend pas que tous les modules soient téléchargés, elle compose immédiatement, quelles que soient les Parts présentes. Notez également que dans l'événement DownloadCompleted il n'y a pas de besoin de mettre de code gérant l'ajout des nouvelles Parts au container.

III-D-3. Composer dynamiquement les Parts téléchargées et utiliser la recomposition

Dans le code ci-dessus, on n'attend pas d'avoir les nouvelles Parts avant d'afficher ou de composer l'application. Cela lève la question suivante, comment les nouveaux modules sont ajoutés. Cela repose entièrement sur la recomposition. En effet, l'import des modules a sa propriété AllowRecomposition à true. Cela indique à MEF que de nouvelles Parts peuvent arriver durant l'exécution, après la composition initiale. Le DeploymentCatalog est recomposable, ce qui signifie qu'une fois que le téléchargement des XAPs est terminé et que les Parts sont découvertes, il indique automatiquement à MEF que les nouvelles Parts sont arrivées. Quand cela arrive, l'import des modules va automatiquement être remplacé par le nouveau set de modules. Il ne remplacera pas les instances de module existantes. Ainsi le DeploymentCatalog et la recomposition sont complémentaires pour le scénario de partitionnement de l'application.

La recomposition requiert que les imports existants de ces contrats soient recomposables, autrement MEF empêche la recomposition ce qui aura pour résultat de lever une exception lors de l'ajout d'un DeploymentCatalog. Par exemple, si la propriété AllowRecomposition=true est enlevée de l'attribut d'import de la propriété modules, MEF va lever une exception quand de nouveaux modules arriveront.

III-D-4. Téléchargement des Parts à la demande après démarrage de l'application et basé sur les actions de l'utilisateur

Le paragraphe précédent étudiait le téléchargement des Parts lors du démarrage de l'application. Un scénario alternatif est une application qui va télécharger les Parts à la demande après qu'elle ait démarrée. Par exemple, pour garder le même exemple que tout à l'heure, les modules ne seront téléchargés que si l'on en a besoin.

Afin de mettre en place ce genre de configuration, un peu plus de travail lors du démarrage de l'application est nécessaire. Les APIs utilisées seront les mêmes, avec juste une addition : au lieu de passer un AggregateCatalog au moment du démarrage, il sera exposé à travers un service qui sera exporté aux autres Parts, leur autorisant ainsi à télécharger les nouveaux XAPs.

Voilà le code illustrant cet exemple à l'aide d'un DeploymentCatalogService :

 
Sélectionnez
using System.ComponentModel;
using System.ComponentModel.Composition.Hosting;

public class App : Application {
  public App() {
    this.Startup += this.Application_Startup;
    this.Exit += this.Application_Exit;
    this.UnhandledException += this.Application_UnhandledException;

    InitializeComponent();
  }

  private void Application_Startup(object sender, StartupEventArgs e)
  {
    DeploymentCatalogService.Initialize();
    CompositionInitializer.SatisfyImports(this);

    var mainPage = new MainPage();
    mainPage.DataContext = ViewModel;
    RootVisual = mainPage;
  }


  [Export(typeof(IDeploymentCatalogService))]
  public class DeploymentCatalogService : IDeploymentCatalogService
  {
    private static AggregateCatalog _aggregateCatalog;

    Dictionary<string, DeploymentCatalog> _catalogs;

    public DeploymentCatalogService()
    {
      _catalogs = new Dictionary<string, DeploymentCatalog>();
    }

    public static void Initialize()
    {
      _aggregateCatalog = new AggregateCatalog();
      _aggregateCatalog.Catalogs.Add(new DeploymentCatalog());
       CompositionHost.Initialize(_aggregateCatalog);
    }

    public void AddXap(string relativeUri, Action<AsyncCompletedEventArgs> completedAction)
    {
      DeploymentCatalog catalog;
      if (!_catalogs.TryGetValue(relativeUri, out catalog))
      {
        catalog = new DeploymentCatalog(relativeUri);

        if (completedAction != null)
          catalog.DownloadCompleted += (s, e) => completedAction(e);
        else
          catalog.DownloadCompleted += (s,e) => DownloadCompleted(s, e);

        catalog.DownloadAsync();
        _catalogs[relativeUri] = catalog;
        _aggregateCatalog.Catalogs.Add(catalog);
      }
    }

    void DownloadCompleted(object sender, AsyncCompletedEventArgs e)
    {
      if (e.Error != null)
      {
        throw new InvalidOperationException(e.Error.Message, e.Error);
      }
    }
  }
    
}
  
  public interface IDeploymentService {
    void AddXap(string relativeUri, Action<AsyncCompletedEventArgs> completedAction);
  }


La classe DeploymentCatalogService expose une méthode statique Initialize qui va initialiser le CompositionHost avec un AggregateCatalog et garder une référence statique vers ce dernier. Le membre statique permet à l'instance exportée du DeploymentCatalogService d'avoir accès à l'AggregateCatalog afin de pouvoir y ajouter de nouveaux catalogues. La méthode AddXap prend l'URI qui sera utilisée afin de créer le DeploymentCatalog correspondant, si celui-ci n'a pas déjà été créé. Elle s'abonne aussi à l'événement DownloadCompleted et démarre le téléchargement asynchrone. Le catalogue est alors immédiatement ajouté à l'AggregateCatalog.

III-D-5. Gestion des erreurs durant le téléchargement

Comme le DeploymentCatalogService implémente le pattern d'événement async, aucune exception ne sera levée durant le téléchargement. Si une exception se produisait, cela lèverait l'événement DownloadCompleted et affecterait l'exception à la propriété Error de la classe d'arguments d'événements. L'exemple ci-dessus le montre. Si une erreur se produit, il est préférable de supprimer l'instance de DeploymentCatalog afin d'en créer une nouvelle. Vous ne pouvez pas la remettre à zéro. Cela ne posera pas de problème de laisser l'instance dans l'AggregateCatalog, cependant comme le catalogue n'a pas été téléchargé, il va simplement retourner une collection de Parts vide.

III-D-6. Mise à jour de la progression

Des notifications sont continuellement envoyées par le DeploymentCatalog durant le téléchargement, permettant ainsi d'indiquer la progression via une ProgressBar par exemple. Pour recevoir ces notifications, il suffit de s'abonner à l'événement DownloadProgressChanged.

L'exemple ci-dessous montre un exemple de prise en charge de ces notifications :

 
Sélectionnez
public class DownloadManagerViewModel, INotifyPropertyChanged {

  public event PropertyChangedEventHandler PropertyChanged;

  public long BytesReceived {get;private set;}

  public int Percentage {get; private set;}

  public void DownloadProcessChanged(object sender, DownloadProgressChangedEventArgs e) {
    BytesReceived = e.BytesReceived;
    Percentage = e.ProgressPercentage;
    RaisePropertyChanged("BytesReceived");
    RaisePropertyChanged("Percentage");
  }

  public void RaisePropertyChanged(string property) {
    var handler = PropertyChanged;
    if(handler!=null) 
      PropertyChanged(this, new PropertyChangedEventArgs(property));
  }
}


La classe DownloadManagerViewModel expose une méthode DownloadProgressChanged qui peut être appelée par l'application quand l'événement survient. Dans cette méthode, les propriétés BytesReceived et Percentage sont définies grâce à l'eventArgs. La méthode RaisePropertyChanged est ensuite appelée afin de notifier l'interface utilisateur des changements survenus.

III-D-7. Annulation du téléchargement

Dans plusieurs scénarios il peut s'avérer utile de permettre à l'utilisateur d'annuler le téléchargement. Par exemple si l'application peut être utilisée en hors-ligne. Pour ces cas-là, le DeploymentCatalog fournit la méthode CancelAsync. Une fois cette méthode appelée, vous ne pouvez plus utiliser le catalogue. Pour reprendre le téléchargement vous devrez créer un nouveau catalogue.

III-D-8. Utilisation du DeploymentCatalog quand l'application est hors-ligne ou Out of Browser

La classe DeploymentCatalog utilise la classe Silverlight WebClient. Quand une application est hors-ligne, cette classe va automatiquement switcher afin d'utiliser le cache du navigateur. Cela signifie que les XAPs déjà téléchargés sont encore accessibles, du moins aussi longtemps qu'ils sont dans le cache. Quand le cache est effacé, les XAPs doivent être re-téléchargés.

III-D-9. Mises en garde contre l'utilisation du DeploymentCatalog

  • L'hôte doit être configuré pour utiliser un DeploymentCatalog, comme vu plus haut ;
  • les assemblages Silverlight mis en cache ne sont pas supportés, même le XAP principal (cela signifie que MEF ne va pas les découvrir ou initier leur télécharger). Il y a deux moyens de contourner ceci :
    • vous pouvez faire en sorte que l'application référence des assemblages partagés. Cela permettra aux XAPs de référencer ces assemblages avec la propriété CopyLocal à false dans les références ;
    • vous pouvez avoir un XAP partagé qui va contenir les assemblages partagés. Tant que le XAP est téléchargé avant que les Parts qui en ont besoin le soient, les références de ce XAP fonctionneront (CopyLocal=false). Cela vous permettra d'introduire dynamiquement de nouveaux contrats qui ne sont pas référencés statiquement.
  • la localisation n'est pas supportée. Seuls les assemblages définis dans le manifeste seront utilisés ;
  • il est impossible d'accéder aux ressources/fichiers en vrac en dehors de l'assemblage au moyen de ressources embarquées ;
  • les catalogues téléchargés ne sont pas copiés dans le système de fichiers quand l'application fonctionne en mode Out of Browser ;

Pour plus de détails sur le DeploymentCatalog, lisez ce post de Glenn Block : Building Hello MEF - Part IV - DeploymentCatalog


précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2010 Jérémie Bertrand. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.