Silverlight - Service References - Error while attempting to find services

Mais qu'elle est donc cette erreur !?

"An error occured while attempting to find services at 'http://localhost/ServiceLogger.svc'"

Contexte de l'erreur

Je cherche à faire des logs avec Log4Net depuis une application Silverlight. J'ai créé une "Business Application Silverlight" hostée par une application ASP.NET. Je développe mon service de Log dans BusinessApplicationLog4Net.Web et par l'intermédiaire d'un "Service Reference", je donnerai accès aux services de Log dans mon application Silverlight (ou du moins je l'espère) :

ASP.NET qui host un WCF Service pour une application Silverlight
Dans l'application Silverlight, je fais "Add Service Reference ..."

Siverlight hosted application - Add Service Reference
Mais au moment de découvrir les Services mis à disposition par l'application ASP.NET, avec Siverlight une erreur survient :

Siverlight - Add Servcice Reference ... - An error occured while attempting to find services
Comme je l'ai déjà indiqué et que lorsque l'on utilise les Services du code est généré il faut donc cleaner rebuilder mais rien n'y fait !

Solution ?

Dans le fichier ASP.NET Web.config :

  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="false" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="false" 
                               multipleSiteBindingsEnabled="true" />
  </system.serviceModel>

Je propose de mettre aspNetCompatibilityEnabled à false. Pourquoi ? Tout simplement parce que j'ai cherché et trouver cette préconisation, également en regardant le détail de cette erreur :

Silverlight - Service Reference - AspNetCompatibility Error
Cette fois mon Service est enfin découvert :

Siverlight - Add Servcice Reference ... - Mon service est enfin découvert
Tout de même cela me parait curieux, je devrais rendre mon Hosting Services Environment incompatible avec ASP.NET pour l'utiliser dans Siverlight ...

Je lance l'application :

Siverlight - aspNetCompatibilityEnabled à false
Quelle belle erreur ! Toutes ces applications demandent que l'utilisateur soit ou non authentifié et le service d'authentification de Silverlight dans ce cas ne reconnait plus mon utilisateur.

C'est perdu !

Conclusion

Et c'est bien dommage car en câblant un bouton sur la View Home de mon application Silverlight :

\\BusinessApplicationLog4Net\BusinessApplicationLog4Net\Views\Home.xaml.cs :

        private void buttonMakeLog_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            var log = new ServiceLogger.ServiceLoggerClient();
            log.LogMessageAsync("click on button produced log on server");

            textBox.Text = "log " + nbLogs.ToString();
            nbLogs++;
        }

Si je clique sur "Ok" et sur mon mon bouton en ayant pris soin de mettre un point d'arrêt dans l'application ASP.NET :

Point d'arrêt dans l'application ASP.NET qui hoste le Service Logger
Clique sur le bouton Make Logs
Clique sur le bouton pour déclencher la méthode buttonMakeLog_Click() de l'application Silverlight qui déclenche un log à travers le service et dans l'application ASP.NET.

Le point d'arrêt est bien déclenché
Je ne dois pas être si loin, je vais continuer à trouver ...

Et comme je suis un fou furieux, en tous cas un curieux et maintenant que le code de mon service est généré dans Silverlight, je remets aspNetCompatibilityEnabled à true et j'exécute cette fois l'erreur est la suivante :

dans :

\\BusinessApplicationLog4Net\BusinessApplicationLog4Net\Service References\ServiceLogger\Reference.cs

j'obtiens :

Cette fois le Log ne sort plus de Silverlight

Solution

Il faut utiliser l'attribut de la Class :

    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
    public class ServiceLogger : IServiceLogger

Pour que cela fonctionne.



Silverlight et Reporting Services (2/2)

Vous êtes peut-être comme moi, resté sur votre fin lors de l'exploration de l'exemple d'Ernesto Herrera sur les Silverlight et Reporting Services. Alors allons plus loin dans le détail des explications du fonctionnement de cette application.

Rappel sur les applications Silverlight et ASP.NET

C'est l'application Web ASP.NET qui Host l'application Silverlight grâce à la page :
ReportingServicesDemoTestPage.aspx
et la ligne :
<param name="source" value="ClientBin/ReportingServicesDemo.xap"/>

Ainsi lorsque vous exécutez l'application dans un navigateur, l'url qui s'affiche est :
http://localhost:52878/ReportingServicesDemoTestPage.aspx#/Home

Et vous obtenez le résultat d'exécution de l'application Silverlight dans la page web Host,
de la façon suivante :

Exécution de l'application Silverlight et Reporting Services dans Chrome
Nous avons configuré "Reporting Services" dans la page de notre application Web ASP.NET : RpDemoWebForm.aspx, ainsi lorsque l'on clique sur un "Sales Order ID" par exemple le "71774", une fenêtre popup s'affiche pour montrer le détail d'un ordre de vente dans un rapport "Reporting Service". Cette fois il s'agit d'une simple page web qui affiche le rapport et l'on obtient le résultat de l'exécution suivant :

Silverlight et les Reporting Services  - Détails d'une vente

Silverlight WCF RIA Services - RIA Control Navigation Page

Comment tout ceci fonctionne t-il ? Attardons nous sur la navigation page Silverlight qui affiche la Grid et découvrons cette page. Ernesto Herrera dit qu'il a simplement ajouter une DataGrid dans la page Home du projet : ReportingServicesDemo\Views\Home.xaml

En fait il fait bien plus que cela :

Page Home Silverlight qui utilise le DomainDataSource pour afficher les données dans une DataGrid
Il a donc ajouté à la page également un riaControls:DomainDataSource.

Cette page Silverlight possède une petite icône de BD dans la colonne de gauche.

Il y a deux balises remarquables "riaControls:DomainDataSource" et "my:ADWLTDomainContext" ce sont ces deux balises qui donnent le résultat de la petite icône BD dans la colonne gauche.

En sélectionnant la balise "ADWLTDomainContext" j'obtiens le résultat graphique suivant :

ADWLTDomainContext - DomainDataSource du Services RIA WCF
L'icône de BD est sélectionnée.

Ajout de riaControls dans la page Silverlight

A fin d'ajouter le riaControls vers le DomainContext vous devez ajouter dans la page Home.xaml les deux XML Name Space :

  xmlns:riaControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.DomainServices" 
  xmlns:domain="clr-namespace:ReportingServicesDemo.Web"

Dans le projet, ajoutez une référence à : System.Windows.Controls.DomainServices

Pour cela vous devez avoir installé le  SDK : RIA Services
Vérification de l'installation du SDK dans :
C:\Program Files\Microsoft SDKs\RIA Services\v1.0\

Et sinon vous pouvez l'installer Ici.

En suite toujours dans la page Home.xaml, on ajoute le code :

                <riaControls:DomainDataSource 
                    AutoLoad="True" 
                    d:DesignData="{d:DesignInstance domain:SalesOrderHeader, CreateList=true}" 
                    Height="0" 
                    LoadedData="salesOrderHeaderDomainDataSource_LoadedData" 
                    Name="salesOrderHeaderDomainDataSource" 
                    QueryName="GetSalesOrderHeaderQuery" 
                    Width="0">
                    <riaControls:DomainDataSource.DomainContext>
                        <domain:ADWLTDomainContext />
                    </riaControls:DomainDataSource.DomainContext>
                </riaControls:DomainDataSource>

A la compilation une erreur :

Error 1
The tag 'ADWLTDomainContext' does not exist in XML namespace 'clr-namespace:ReportingServicesDemo.Web'.

Cette erreur semble nous indiquer que le domain services n'a pas été créé dans ReportingServicesDemo.Web. En effet dans l'application de référence on peut constater l'existence de deux fichiers :

Création du WCF RIA Services

Création de la DataSource du DomainContext

Il s'agit d'une erreur ou bien d'un problème de version mais dans le projet 
d'Ernesto Herrera les deux fichiers :

ADWLTDomainService.cs
ADWLTDomainService.metadata.cs

Sont dans le répertoire racine, tandis que si on les déplace dans le répertoire Services et que l'on fait :

Visual Studio - Show Data Sources
Un répertoire \\ReportingServicesDemo\ReportingServicesDemo\Properties\DataSources est créé il contient le fichier : ReportingServicesDemo.Web.ADWLTDomainContext.datasource généré automatiquement.

Et pourtant mon application ne fonctionne toujours pas !

Silverlight WCF RIA Services Error NotFound !
C'est un piège dans lequel on tombe parfois avec Visual Studio et tout le code généré automatiquement mais les fichiers .datasource ne sont pas le problème. Dans ce cas je préconise de recommencer l'application à zéro.

Je créé une Business Application Silverlight 5.0
J'importe la base AdventureWorksLT2008_Data.mdf dans App_Data
Je recommence mon Modèle Entity, je n'inclus que les tables
Pour changer un peu je vais me passer de la Class Orders.cs et j'attaque directement SalesOrderDetail ma Query Link s'écrit donc :

        public IQueryable<SalesOrderDetail> GetOrderDetails(int OrderID)
        {
            AdventureWorksLT2008_DataEntities adw = new AdventureWorksLT2008_DataEntities();

            var query = from c in adw.SalesOrderDetail.Include("Product")
                        where c.SalesOrderID == OrderID
                        select new SalesOrderDetail
                        {
                            //SalesOrderID = OrderID,
                            //SalesOrderDetailID = c.SalesOrderDetailID,
                            //ProducName = c.Product.Name, // BRY 
                            //OrderQty = c.OrderQty,
                            UnitPrice = c.UnitPrice //,
                            //UnitPriceDiscount = c.UnitPriceDiscount,
                            //LineTotal = c.LineTotal
                        };

            return query;
        }

Et l'application tombe en marche mais lorsque je clique sur le bouton me permettant de voir les détails d'un ordre :

Reporting Services - Error to LINQ Entity

Le DomainContext est généré automatiquement dans :
BusinessApplicationReportingServices\BusinessApplicationReportingServices
\Generated_Code\BusinessApplicationReportingServices.Web.g.cs

Alors je réécris ma query linq, le plus simplement du monde :

        public IQueryable<SalesOrderDetail> GetMyOrderDetails(int OrderID)
        {
            AdventureWorksLT2008_DataEntities adw = new AdventureWorksLT2008_DataEntities();

            var query = from c in adw.SalesOrderDetail//.Include("Product")
                        where c.SalesOrderID == OrderID
                        select c;
            return query;
        }

Et le tout tombe en marche :

Silverlight - Reporting Services

Conclusion

DomaineServices, Reporting Services et Silverlight cela parait tout simple mais les pièges sont nombreux notamment à cause de tout ce code généré il faut souvent rebuilder, mettre à jour les schémas parfois cleaner  pour que la solution fonctionne finalement.

Finalement :
Je réintègre la class Orders.cs pour effectuer la jointure je réécris le Report Services pour obtenir le résultat suivant :

Exécution de l'application Silverlight WCF RIA - Reporting Services

Exécution de l'application Silverlight WCF RIA - Reporting Services - Détails d'un ordre de vente

Download Source Code

Requirements :
Visual Studio 2010 SP1
Reporting Services
WCF Ria Services for Silverlight 5.0
SQL Server EXPRESS 2008








Silverlight et Reporting Services (1/2)

Voilà un sujet qu'il est très intéressant, laissons de côté le choix de Crystal Report ou de Reporting Services avec une page de lecture Ici.

Alors comment réaliser une intégration rapide avec une couche présentation Silverlight, un accès aux données Entity Framework et des Reporting Services aux milieu de tout cela.

Je base mon exploration sur l'exemple du site CodeProject :
http://www.codeproject.com/Articles/97452/Integrate-Reporting-Services-with-Silverlight-and
Integrate Reporting Services with Silverlight and RIA Services

Dans le Projet ReportingServicesDemo.Web le répertoire App_Data est vide !
Mais où est la BD ? C'est toujours le même problème avec SQL Server les .mdf ne sont jamais livrés et vous trouvez cela normal ?

J'ouvre le fichier Web.config pour inspecter la partie "connectionStrings" du projet et là je peux lire :
Data Source=WSCORSIS032\SQLSERVER08;Initial Catalog=AdventureWorksLT2008;

Cela ne risque pas de fonctionner sur votre plateforme de développement.

Trouver la BD AdventureWorksLT2008

Initial Catalog me dit qu'il faut chercher une BD avec un nom du genre AdventureWorksLT2008 ...
On la trouve facilement dans le CodePlex :

SQL Server BD AdventureWorksLT2008
C'est sûr le projet de Ernesto Herrera ne fait que 801 Ko tandis qu'à lui seul le fichier AdventureWorksLT2008_Data.mdf fait 8 512 Ko, CQFD.

J'utilise une version Express de SQL Server 2008 cela va-t-il fonctionner ?

Le projet Reporting Services with Silverlight

En gros, je vais essayer de : 
  • importer la BD AdventureWorksLT2008_Data dans le répertoire App_Data du projet
  • modifier le fichier Web.config pour que la connectionStrings soit correcte
  • effectuer une mise à jour du Modèle d'Entities

Import de AdventureWorksLT2008_Data  dans le projet

Une fois le .zip téléchargé et dézippé. Dans mon projet ReportingServicesDemo.Web, je fais un "Add->Existing Item..." je pointe sur AdventureWorksLT2008_Data.mdf et j'importe dans le répertoire prévu à cet effet : App_Data. Me voilà avec la BD importée dans mon projet :

Import de AdventureWorksLT2008 dans l'App_Data du projet ReportingServicesDemo.Web

Mise à jour de l'ADO.NET Entity Data Model

Dans Visual Studio j'ouvre le fichier ADWLTModel.edmx (Entity Data Model). Je vais tenter une mise à jour du modèle depuis la BD :

ADWLTModel.edmx mise à jour depuis la BD
Donc bouton droit-> Update Model from Database ... :

ADWLTModel Mise à jour depuis la BD AdventureWorksLT2008
Quelle jolie boite de dialogues ! Je peux y voir déjà que Visual Studio aura repéré la BD que j'ai importé sur mon disque dur mais pas encore celle de mon projet.

Visual Studio me propose dans de mettre à jour la ConnexionString de mon fichier Web.config.

Mais soyons fou et modifiions cette chose là !

Je clique sur "New Connection..." :

Entity Data Model - New Connection ...
Avec le bouton "Browse..." je vais pointer sur la BD que je viens d'importer dans mon projet :
\\ReportingServicesDemo\ReportingServicesDemo.Web\App_Data\AdventureWorksLT2008_Data.mdf

Je me retrouve avec une nouvelle chaîne de connexion :

ADWLTModel Mise à jour depuis la BD AdventureWorksLT2008 nouvelle chaîne de connexion
Pour les amateurs d'application Web ASP.NET, vous avouerez que cette nouvelle chaîne de connexion est bien plus souhaitable au passage elle est bien plus portable.

On note le changement de SQL Server de SQL Server 2008 à SQLEXPRESS ...
On note également l'utilisation du |DataDirectory| ce qui est classique pour une application ASP.NET.

Je clique sur "Next..." :

Entity Data Model - Mise à jour depuis la BD importée dans App_Data
Puis sur "Finish" ... 

Grossière erreur, je viens d'ajouter les tables aux tables déjà existantes dans mon Modèle Entities (.edmx). Dans Visual Studio c'est la panique un rebuild du projet ReportingServicesDemo.Web me sort une séries d'erreurs incontrôlables.

Je passe par "Delete" "Refresh" bref ... :


Au bout d'un moment je sélectionne tous les objets du fichier ADWLTModel.edmx puis les supprime, sauvegarde le modèle qui est maintenant vide, je fais à nouveau un Add des Tables des Views et des Procédures stockées depuis ma base de données et le projet fonctionne :

Silverlight et Reporting Services - Exécution du projet après mise à jour du Modèle Entities
Le projet d'Ernesto Herrera : Integrate Reporting Services with Silverlight and RIA Services fonctionne maintenant sur ma plateforme de développement et, de plus il est portable ...

Conclusion

Ce n'est pas si facile lorsque l'on est confronté pour la première fois à ce genre de chose. Par la suite et par la pratique, on prend de bonnes habitudes. On cesse de paniquer devant des erreurs incontrôlables et l'on reprend les choses simplement de façon propre.

L'idée c'est que ce qui est généré automatiquement doit être mise à jour automatiquement. Vous auriez pu par exemple mettre à jour la connexionString dans le Web.config manuellement cela aurait été une erreur de mon point de vu.

Si Ernesto Herrera avait utilisé le répertoire App_Data sont projet aurait été nettement plus portable car il aurait suffit d'importer AdventureWorksLT2008_Data.mdf dans ce répertoire pour que son projet fonctionne.

Download Sample
Requirements :
Visual Studio 2010
Silverlight v5.0
Reporting Services

Pour aller plus loin 

Quoi de neuf dans le Microsoft Framework .NET ?

Un peu d'histoire, qu'elles sont les étapes importantes, les modifications apportées au .NET Framework de Microsoft. On me pose souvent cette question lorsque l'on me demande si j'ai déjà travaillé avec telle ou telle version du Framework.

Voici donc pour ceux qui aiment l'histoire du développement logiciel de Microsoft .NET, les dates des releases des différentes versions du framework :
Overview of .NET Framework release history
GenerationVersion numberRelease dateDevelopment toolDistributed with
1.01.0.3705.02002-02-13Visual Studio .NETN/A
1.11.1.4322.5732003-04-24Visual Studio .NET 2003Windows Server 2003
2.02.0.50727.422005-11-07Visual Studio 2005Windows Server 2003 R2
3.03.0.4506.302006-11-06Expression BlendWindows Vista, Windows Server 2008
3.53.5.21022.82007-11-19Visual Studio 2008Windows 7, Windows Server 2008 R2
4.04.0.30319.12010-04-12Visual Studio 2010N/A
4.54.5.50709.179292012-08-15Visual Studio 2012Windows 8, Windows Server 2012

Source : WikipediA on y trouvera les grands principes du CLR.

Ne parlons pas de la version 1.0 qui ne fonctionnait pas. D'ailleurs je ne connais personne qui ait démarré en 2002 avec cette version 1.0. On commencera à parler de développement possible avec le .NET Framework que bien plus tard.

DotNET Framework 1.1

ASP.NET Mobile Controls

Les contrôles mobiles sont maintenant intégrés au framework. Le nom Mobile Internet Toolkit est remplacé par ASP.NET Mobile Controls. 

Les contrôles mobiles sont : AdRotator Calendar Command CompareValidator CustomValidator Form Image Label Link List MobilePage ObjectList Panel PhoneCall RangeValidator RegularExpressionValidator RequiredFieldValidator SelectionList StyleSheet TextBox TextView ValidationSummary

Les contrôles mobiles sont extensibles, vous pouvez utiliser la composition et l'héritage pour créer de nouveaux contrôles.

ADO.NET

L'objet DataReader expose maintenant une propriété HasRow pour savoir si l'objet retourne des Lignes sans avoir besoin de faire appel à la fonction Read.

L'objet Connection possède maintenant une méthode EnlistDistributedTransaction pour établir manuellement des transactions distribuées.

Exécution côte à côte

Différentes versions d'une application peuvent s'exécuter en même temps sur un même ordinateur. Une application peut choisir les composants avec lesquels elle va s'exécuter.

IPv6

Le framework .NET support maintenant IPv6 ...

DotNET Framework 2.0

La version 2.0 est importante, on va enfin pouvoir travailler.
Support des plateformes 64-bits
ACL : Access Control List utilisé pour accorder (grant) ou supprimer (revoke) la persmission d'accès au ressources d'un ordinateur.

Apparition des Generics et des Collection Generics.

Globalisation : GetCultureInfo et CultureInfo

ADO.NET 

Support de UDT User-define Types, types définis par l'utilisateur. Apparition des types de données XML.

ASP.NET

Grandement amélioré, les contrôles sont améliorés, on peut éditer modifier des données sans écrire de code. On peut écrire une table de dépendance de cache dans SQL Server.
Apparition des Web Parts. Menu de navigation. Master Page. 
Prise en compte des différents navigateurs, le rendu des contrôles est compatible des standards XHTML 1.1.

Débogueur

Il supporte le mode édit et continuer, vous pouvez modifier le code source et reprendre l'exécution du programme ...

Support de SMTP

Amélioré, on peut maintenant envoyer à plusieurs destinataires, des pièces attachés mettre en copie et en carbon copie ... Et avant c'était comment ? 
Espaces de noms System.Net.Mail et System.Net.Mime

Filtrage des données de Trace

Le système de Trace et Log des données permet maintenant de les filtrer ...

Web Services

Support de SOAP 1.2 et de WS-I Basic Profile 1.0

XML Améliorations

Améliorations du Processeur : XSL Transformation (XSLT)
XmlReader
XmlWriter
XPathNavigator

Langage Visual Basic

Certaines améliorations du Langage Visual Basic sont apportée pour lui donner à peu prêt le même niveau qu'en C# ...

Conclusion

Il y a plein d'autres améliorations du .NET Framework que je ne détaille pas ici, j'ai tenté de résumer mais c'est à se demander comment pouvait-on faire avant ...

DotNET Framework 3.0

La version 3.0 du Framework est sortie pour regrouper les technologies suivantes dans le SDK (Software Development Kit) :
  • Windows Communication Foundation
  • Windows Presentation Foundation
  • Windows Workflow Foundation
  • Windows CardSpace

DotNET Framework 3.5

C'est WCF (Windows Communication Foundation) la partie importante dans cette release. WCF est maintenant intégré dans le .NET Compact Framework pour les applications mobiles.

ASP.NET

Ajax Enable Web Site, les contrôles ScriptManager et UpdatePanel ont été ajouté ainsi on peut rafirachir une page sans la recharger entièrement.
Support IntelliSense pour JavaScripts.
Les fonctions d'authentification, de gestion des rôles et des profile sont maintenant exposées à travers un service-web qui peut être hébergé dans une application WCF, ainsi plusieurs ASP.NET applications peuvent partager ces même services.
Nouveau Data Contrôle ListView
Nouvelle source de données LinqDataSource
De nouveaux outils permettent une meilleur intégration avec IIS 7.0

Add-Ins et extensibilité

Namespace System.AddIn.dll
Fonctionnalités :
Discovery
Activation
Isolation
Sandboxing
UI Composition
Versioning

On se croirait dans MEF (Managable Extensible Framework) ... curieux je n'avais pas vu cet aspect des choses.On va voir que MEF sera intégré au Framework 4.0

Common Language Runtime

Une nouvelle collection : HashSet<T>, modélisation d'un ensemble mathématique de valeurs toutes différentes et non ordonnées.

Diagnostic : nouvelle class EventSchemaTraceListener pour des composants hétérogènes qui traversent des thread des AppDomain.

Linq

Étendu par le .NET 3.5 donne la possibilité de faire des requêtes sur des Tables SQL Server, des ADO.NET DataSet, des XML Documents, et des collections du type IEnumerable et IEnumerable<T>.

Expression Trees

Namespace : System.Linq.Expressions
Permet de représenter des expressions du code sous forme de données.
Mais à quoi cela peut-il bien servir ? Ici.

DotNET Framework 4.0

.NET Framework 4 Client Profile : sous ensemble du .NET Framework 4 optimisé pour les applications clientes.
Améliorations de l'énumération de système de fichiers ??? Et oui on améliore encore c'est trucs là !

Managed Extensibility Framework

Managed Extensibility Framework (MEF) est une nouvelle bibliothèque dans le .NET Framework 4 
qui vous aide à générer des applications extensibles et composables. MEF 
vous permet de spécifier des points où une application peut être étendue, 
d'exposer des services à offrir à d'autres applications extensibles et 
de créer des parties en vue de leur utilisation par des applications extensibles. 
Il facilite également la détectabilité des parties disponibles en fonction des métadonnées, 
sans que les assemblys pour les parties aient besoin d'être chargés.

Dynamic Language Runtime (DLR)

Dynamic Objects, Python est un exemple de langage dynimaque

TBC (To Be Continued)

Objets MVVM

Quels sont les objets MVVM nécessaires à l'exécution des applications C# .NET basées sur le Modèle M-V-VM ?

Objets dérivés de l'interface ICommand
RelayCommand
DelagateCommand
DelegateCommandWithParam

Objets dérivés de INotifyPropertyChanged
ObservableObject

PropertyObserver

Autres objets
EventToCommand
Mediator
Messenger

ViewModelBase : INotifyPropertyChanged

View modèles de base
ViewModelBase : peut dériver de DependencyObject
Tous les ViewModel vont dériver de ViewModelBase

Controler de base

WPF Drag & Drop in Grid Sample

Drag and Drop, ce n'est peut être pas si facile. Bien souvent il manque quelques astuces de bases comme par exemple quand on Drop dans une Grid on aimerait bien savoir dans quelle cellule on a dropé l'objet.

Alors on part de l'exemple MSDN :

Drag and Drop on a user control

MSDN Drag and Drop Sample

On écrit une couleur dans le TextBox et en la dropant  dans un des cercles on change la couleur du cercle, on peut copier un cercle en le dropant en maintenant la touche contrôle appuyée.

C'est intéressant mais il nous manque tout un tas de choses. Ce que j'aimerais faire c'est dropper des objets dans une Grid savoir où il sont dans la Grid et pouvoir commencer réellement à jouer avec mes objets.

Il est par exemple intéressant de jouer avec les propriétés de ma Grid et d'y déplacer les objets :

Grid.SetRow(object,x), Grid.SetColumn, Grid.SetColumnSpan,Grid.SetRowSpan

Je sais maintenant déplacer mes objets dans la Grid mais où sont -ils ?

Grid.GetRow(object,x),  object.GetValue(Grid.RowProperty)

Et voilà que je peux commencer à jouer.

Mais je voudrais aller plus loin et pouvoir dropper mes objects mais qu'ils ne se chevauchent pas. Pouvoir leur donner la taille que je veux en la changeant dans un menu contextuel et toujours en évitant qu'ils ne se chevauchent.

Voilà l'exemple que je vous propose :

WPF Drag&Drop Sample CanDrop and CanResize

Vous trouverez dans le code source suivant, la création dynamique d'objets dans la grille pour détecter où les objets sont droppés. Et tout un tas d'autres astuces vous permettant de réellement bien commencer avec le Drap&Drop en WPF.

Requirements :
WPF .NET 4.0
Visual Studio 2010
Microsoft.Practices.Prism
Microsoft.Practices.Prism.Interactivity
Microsoft.Practices.Prism.UnityExtension
Microsoft.Practices.Prims.Unity

Download and Setup Prism Library 5.0 for WPF

Inutile de préciser que les packages nugets sont difficiles à installer en ce qui concerne Prism et ses différentes version, en constante modification de version de namespace... bref la résolution de vos problèmes passera peut être par l'exécution de la commande : 

PM> Install-Package CommonServiceLocator dans la "Console du gestionnaire de package" 

suivie de 

PM> Install-Package Prism.UnityExtensions -Version 4.1.0

Download Sample

Si vous souhaitez aller plus loin et notamment en fonctionnant avec le modèle MVVM, je vous recommande vivement les articles suivants :

Drag and Drop in WPF
Drag and Drop in WPF - Part II

Ce que j'apprécie vraiment dans ce Sample c'est la mise en place d'un "Adorner", une parure qui à travers  une Property DragAdornerTemplate liée à une DependencyProperty affiche une image en transparence lorsque l'on déplace les objets. Dans le Xaml de la façon suivante :

<ListBox dd:DragDrop.DragAdornerTemplate="{StaticResource PupilDragAdorner}">

Voilà je vous ai tout dis sur mes recherches en matière de Drag & Drop avec WPF. Si vous allez plus loi merci de nous faire part de vos avanceés.

Vous avez apprécié cet article, laissez un commentaire.

Prism InteractionRequest Popup a Window (2)

Dans un post précédent : Prism InteractionRequest Popup a Window (1)on a vu l'utilisation de prism:InteractionRequestTrigger pour déclencher une InteractionRequest permettant de récupérer le ViewModel d'une fenêtre secondaire et à l'intérieur de ce ViewModel une propriété publique SelectedItem. Ainsi dans la fenêtre principale on peut afficher le choix effectué par l'utilisateur dans la fenêtre secondaire :

Récupération du choix utilisateur effectué dans le module Prism HelloWorldModule

Prism Popup a Window comment ça marche ?

La fenêtre Clients implémente l'interface IPopupWindowActionAware de la Class Library Infrastructure cette interface possède deux propriétés :

namespace Infrastructure.InteractionRequests
{
    public interface IPopupWindowActionAware
    {
          Window HostWindow { get; set; }
          Notification HostNotification { get; set; }
    }
}

Et là on est au coeur de cet exemple, l'objet PopupWindowAction dérive de TriggerAction<FrameworkElement>, il utilise des DependencyProperty pour modifier l'apparence de la Popup et override la fonction void Invoke(object parameter) pour construire la Popup et faire à la fin :

// IsModal 
// public static readonly DependencyProperty IsModalProperty
if (this.IsModal)
{
wrapperWindow.ShowDialog();
}
else
{
wrapperWindow.Show();
}

Ainsi le fenêtre secondaire est ou non Modale. Oui je passe rapidement et vous laisse le soin de lire ces sources et d'évaluer leur complexité.

Prism Popup a Window conclusion

Déjà l'on voit qu'une réflexion intense doit être menée à la naissance d'un projet WPF Prism afin de décrire le comportement des différents modules et de leurs interactions. Cette réflexion (ou conception) donne naissance au projet de Class Library Infrastructure qui s'occupe du comportement MVVM des objets utilisés et interagissants dans les différents modules.

Mais ce n'est pas nouveau, il vaut mieux toujours, passer un peu de temps en conception afin d'être certain que l'architecture choisie répondre aux besoins que de se jeter à pisser du code.

Prism InteractionRequest Popup a Window (1)

Etude des sources de Damian Cherubini trouvées dans son SkyDrive et son exemple de PopupWindowsAction Sample.

Les objets mis en oeuvre sont :
Prism.UnityExtensionsPrism.UnityBootstrapper
<i:Interaction.Trigger
<prism:InteractionRequestTrigger
Prism.Interactivity.InteractionRequest
Prism.Commands.DelegateCommand
Prism.Events.EventAggregator

Architecture de du projet PopupWindowAction Sample

Un projet Windows Application "HelloWorld" utilise Prism et Unity pour charger le module "HelloWorldModule" :

namespace HelloWorld
{
    public class Bootstrapper : UnityBootstrapper
    {
        protected override void ConfigureModuleCatalog()
        {
            base.ConfigureModuleCatalog();

            ModuleCatalog moduleCatalog = (ModuleCatalog)this.ModuleCatalog;
            moduleCatalog.AddModule(typeof(HelloWorldModule.HelloWorldModule));
        }    

Le module HelloWorldModule utilise un projet Class Librairy "Infrastructure" qui propose une interface IRegionManagerAware et un objet PopupWindowAction qui vont permettre dans les Views du HelloWorldModule de popuper des Window de la façon suivante :

<UserControl x:Class="HelloWorldModule.Views.HelloWorldView"

    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:inf_int="clr-namespace:Infrastructure.InteractionRequests;assembly=Infrastructure"
    xmlns:local="clr-namespace:HelloWorldModule.Views"
   ...>
<i:Interaction.Triggers>
        <prism:InteractionRequestTrigger SourceObject="{Binding SelectClientRequest, Mode=OneWay}">
            <inf_int:PopupWindowAction>
                <inf_int:PopupWindowAction.WindowContent>
                    <local:SelectClientView />
                </inf_int:PopupWindowAction.WindowContent>
            </inf_int:PopupWindowAction>
        </prism:InteractionRequestTrigger>
</i:Interaction.Triggers>

Des fenêtres secondaires vont permettre à l'utilisateur de choisir une série d'actions dont le résultat sera afficher dans la fenêtre principale : HelloWorldView.xaml.

PopupWindowAction Sample Prism InteractionRequest

PopupWindowAction Sample Exécution

Ici je vais focusser sur le dernier exemple de popupwindow dont la propriété SourceObject est bindée sur SelectClientRequest.

Une fois les Interaction.Trigger correctement configurées, il faut encore un bouton dont la propriété Command est bindée sur RaiseSelectClient :

<Button Margin="5" Content="Raise Select Client View" Command="{Binding RaiseSelectClient}" />

Le résultat sera afficher dans TextBlock dont la propriété Text est Bindée sur la propriété Result du ViewModel :

<TextBlock Margin="5" FontWeight="Bold" Foreground="DarkRed" Text="{Binding Result}"/>

On obtient ainsi à l'exécution de la fenêtre principal :
\\PopupWindowActionSample\HelloWorldModule\Views\HelloWorldView.xaml

Fenêtre principale de PopupWindowAction Sample
Et lorsque l'on clique sur le bouton "Raise Select Client View", on obtient la fenêtre cliente suivante  :

Client Window View
En sélectionnant "John Doe" et en cliquant sur "OK" la fenêtre principale récupère le choix utilisateur et l'on obtient le résultat suivant dans la fenêtre principal :
PopupWindowAction Sample après un choix utilisateur
On a donc bien un allé et retour d'une information, d'un choix utilisateur effectuer dans une PopupWindow et que l'on récupère pour l'afficher dans le Module Principal.

PopupWindowAction comment ça marche ?

D'une part, la propriété SourceObject (EventTriggerBase.SourceObject) de l'InteractionRequestTrigger est bindée sur la commande SelectClientRequest dans la View et dans le ViewModel on trouve le code C# suivant :

namespace HelloWorldModule.ViewModels
{
    public InteractionRequest<Notification> SelectClientRequest { get; private set; }
    public ICommand RaiseSelectClient { get; private set; }


    public HelloWorldViewModel()
    {
        this.SelectClientRequest = new InteractionRequest<Notification>();
        this.RaiseSelectClient = new DelegateCommand(this.OnRaiseSelectClient);

Avec l'utilisation de l'objet Notification, on rentre dans l'utilisation de la DLL, Microsoft.Practices.Prism.Interactivity.dll :
Microsoft.Practices.Prism.Interactivity, Notification et Confirmation
D'autre part, la commande du bouton "Raise Select Client View" est bindé sur la DelegateCommand RaiseSelectClient qui exécute l'Action OnRaiseSelectClient :

private void OnRaiseSelectClient()
{
this.SelectClientRequest.Raise(
  new Notification { Title = "Clients" });
}

Ce même mécanisme permet aux deux fonctions OnRaiseConfirmation et OnRaiseNotification de modifier la propriété Result du HelloWorldViewModel pour indiquer le résultat obtenu dans le TexBlock dont la propriété Text est Bindée sur le Result.

Dans le cas de OnRaiseSelectItem, on récupère la propriété string SelectedItem du SelectItemViewModel pour afficher le résultat.

Utilisation de l'EventAggregator pour récupérer les données du Modèle

Dans le cas de OnRaiseSelectClient, pour récupérée des données structurées du model (ClientData) , on utilise alors le mécanisme d'EventAggregator.

Dans le HelloWorldViewModel on s'enregistre à l’évènement : ClientSelectedEvent et on y inscrit l'Action ClientSelected qui sera exécutée sur réception de l'évènement :

public HelloWorldViewModel()
{

    this.eventAggregator = ServiceLocator.Current.GetInstance<EventAggregator>();
    this.eventAggregator.GetEvent<ClientSelectedEvent>().Subscribe(this.ClientSelected);
    ...
}


public void ClientSelected(ClientData client)
{
if (client != null)
{
this.Result = "The user selected the client: " + client.Name;
}
else
{
this.Result = "The user didn't select a client.";
}
}

Dans le SelectClientViewModel, on publie les données choisies par l'utilisateur :

public ClientData SelectedClient { get; set; }

protected void OkAction()
{
this.eventAggregator.GetEvent<ClientSelectedEvent>().Publish(this.SelectedClient);

if (this.HostWindow != null)
{
this.HostWindow.Close();
}
}

Et voilà comment cela fonctionne ...

PopupWindowAction le projet Infrastructure

Si je m'arrêtais là, je passerais à côté de l'un des objets principaux de ce projet. De façon classique Damian a créé un projet dans lequel il range ce qui pourrait faire partie d'un framework de base.

Et pour afficher une PopupWindow sur InteractionRequestTrigger Damian définit sa propre PopupWindowAction qui dérive de TriggerAction :

namespace Infrastructure.InteractionRequests
{
    /// <summary>
    /// TODO: Update summary.
    /// </summary>
    public class PopupWindowAction : TriggerAction<FrameworkElement>
        /// <summary>
        /// The content of the child window to display as part of the popup.
        /// </summary>
        public static readonly DependencyProperty WindowContentProperty =
            DependencyProperty.Register(
                "WindowContent",
                typeof(FrameworkElement),
                typeof(PopupWindowAction),
                new PropertyMetadata(null));

Un peu d'humour dans les commentaires cela ne fait pas de mal, j'adore la rubrique TODO !

C'est objet est utilisé dans l'InteractionRequestTrigger que l'on a vu au tout début de cet article.

Franchement, je ne trouve pas cela si simple ... Et je reviendrais sur cet exemple pour compléter mon analyse de ce projet que je trouve extrêmement intéressant. Tout ce code pour afficher une ChildWindow c'est véritablement bluffant !