Welcome to NetFxFactory Sign in | Join | Help

articles

  • Création d'une liste personnalisée SharePoint 2007

    Introduction

    SharePoint 2007 est une révolution dans le monde SharePoint. En effet, il apporte un lot considérable de nouveautés dans le développement. L’architecture a été revue entièrement, elle est basé sur ASP.NET 2.0 et offre même la possibilité d’utiliser AJAX avec le SP1 de SharePoint. SharePoint est devenue à la fois un portail web avec ses template de site, une plateforme de développement avec son framework, et une solution serveur d’entreprise avec son server de recherche et d’indexation ou encore avec Excel Services. Beaucoup d’articles sur internet parlent de la création de WebPart dans SharePoint, mais il n’y a pas que les WebParts dans cette énorme plateforme de développement. Dans cette article nous allons voir comment faire une liste personnalisée qui affiche des vidéos de Youtube et quelles sont les avantages par rapport a la création d’une webpart qui aurait fait la même chose. Avec le temps je me suis rendu compte qu’il fallait rester au plus prés de sharepoint pour pouvoir offrir aux nouvelles fonctionnalités toute la puissance de sharepoint en terme de recherche, de trie ou de filtre.

    Les outils de développements

    Pour développer avec SharePoint, il vous faut Visual studio 2008 ou Visual studio 2005 avec les VseWSS (WSS Visual studio extension). C’est la version Visual Studio 2005 qu’on utilisera. Ces extensions sont téléchargeables sur le site de Microsoft.com. Une fois que vous avez installé les extensions, plusieurs nouveaux types de projets apparaissent dans Visual studio 2005. La copie d’écran a été faite avec les extensions WSS (Windows SharePoint Services) et MOSS (Microsoft Office SharePoint Server).

    Les types de projets sont :

    Type de projet

    Description

    Team Site Definition

    Création d’un modèle de site de collaboration

    Blank Site Definition

    Création d’un modèle de site Vide

    List Definition

    Définition d’une liste sharepoint personnalisé

    WebPart

    Création d’une webpart

    Empty

    Projet vide

    Sharepoint Server Sequential Workflow

    Création d’un workflow séquentiel

    Sharepoint Server State Machine

    Création d’un workflow machine à état

    Les avantages de l’utilisation des extensions pour Visual Studio sont le débogage et le déploiement qui se font en appuyant sur la touche F5. Prenons un exemple, la création d’un site de collaboration. Je créé un projet de type Team Site Definition que je nomme MyTeamSite :

    On peut regarder ce qui se trouve dans l’explorateur de solution :

    Nous avons la définition du site avec onet.xml, la page d’accueil default et le Provisioning Handler qui gère les événements d’activation et de remplissage du site. Je vais dans le code SiteProvisioning.cs et sur la gestion de l’événement OnActivated je mets du code :

    public void OnActivated(SPFeatureReceiverProperties properties)
    {
    SPWeb web = (SPWeb)properties.Feature.Parent;
    if (web == null)
    throw new Exception("Ne peut récupérer le site web parent");
    web.TreeViewEnabled = true;
    web.Update();
    }

    Avant les extensions, il fallait compiler et déployer à la main, maintenant il suffit d’appuyer sur la touche magique F5. Lorsqu’on appuit dessus Visual Studio compile, package et déploit la feature. Si l’on regarde d’ailleurs dans l’explorateur Windows ce qui a été créé voici ce que l’on obtient :

    Nous avons un setup.bat qui installera ou désinstallera la feature, nous avons aussi un WSP qui est le package créé par visual studio et un dossier solution qui contient les informations sur la features, fichier XML de déploiement etc…

    Ensuite il lance internet explorer sur le site racine de SharePoint.

    On créé un site SharePoint du type MyTeamSite :

    Lorsqu’on clique sur Create le site passe en Provisioning et entre donc dans l’événement OnActivated ce qui nous permet de déboguer notre feature comme si on déboguer un site web.

    Si l’on regarde notre site on a bien le treeview qui est activé comme on lui a demandé :

    La Custom list

    Mais revenons a ce qui nous intéresse, la custom list. Pour cela on va créer une Custom list qu’on appellera youtubeList :

    Le template de site demande quelle liste doit être considérée comme liste de base, dans notre exemple on choisira CustomList et on demandera à Visual studio de créer une instance de cette liste :

    Visual studio créer automatiquement les fichiers nécessaire a la création de la liste :

    On reconnait les pages de la liste (AllItems.aspx), d’affichage d’un item (DispForm.aspx) d’édition d’un item (EditForm.aspx) et d’ajout d’un item (NewForm.aspx)

    La liste est une collection de content type, on va donc ajouter un type de contenu a notre projet. Clique avec le bouton droit de la souris sur le projet Add/ New item

    On ajout un ContentType nommé YoutubeItem. Il demande quel est le type de base de l’item. On Choisira item dans la liste :

    Si on regarde dans l’explorateur de solution l’item n’apparait pas. En fait il faut afficher tous les fichiers et aller dans pkg, la le content type apparait :

    On va commencer par le content type

    Content Type

    Pour paramétrer le content type on va aller dans elementManifest.xml. Dans le nœud FieldRefs on va ajouter les 3 propriétés que l’on veut en plus de notre content type Item. Item contenant déjà Title on ne va pas le renseigner, comme il s’agit d’un héritage youtubeitem contiendra par défaut une propriété Title.

    Voici donc le code :

    <?xml version="1.0" encoding="utf-8"?> 
    <Elements xmlns="http://schemas.microsoft.com/sharepoint/"> 
    <ContentType Name="YoutubeItem" ID="0x010086ac23fd903c468faf40fdc673a0a2fa" Group="Development" Version="0"> 
    <FieldRefs> 
    <FieldRef Name="Description" ID="{8F51D2FB-1F5B-4c09-96EE-FD53A6805CFD}"/> 
    <FieldRef Name="MovieID" ID="{3DA59372-F977-477c-B4FC-7DF2BF673295}"/> 
    <FieldRef Name="Preview" ID="{0BDE7F01-B6EC-49fd-8890-F5E7050DFFA0}" 
    ShowInDisplayForm="TRUE" 
    ShowInFileDlg="FALSE" 
    ShowInEditForm="FALSE" 
    ShowInNewForm="FALSE"/> 
    </FieldRefs> 
    </ContentType> 
    </Elements>

    On spécifie que pour l’on veut la prévisualisation que dans l’affichage de l’élément.

    Maintenant que notre content type est fait on peut aller dans la définition de la liste

    La définition de la liste

    On ouvre schema.xml. La première chose à faire et de lier le content type a la liste. Voici la définition de mon content type dans elementManifest.xml :

    <ContentType Name="YoutubeItem" ID="0x010086ac23fd903c468faf40fdc673a0a2fa" Group="Development" Version="0"> 

    Je change l’ID et le nom du content type dans la définition de ma liste :

    <ContentTypes> 
    <ContentTypeRef ID="0x010086ac23fd903c468faf40fdc673a0a2fa"> 
    <Folder TargetName="Youtube Item"/> 
    </ContentTypeRef> 
    <ContentTypeRef ID="0x0120"/> 
    </ContentTypes>

    Maintenant je spécifie les champs de ma liste qui doivent correspondre aux champs définis dans mon content type :

    <Fields> 
    <Field Name="Description" ID="{8F51D2FB-1F5B-4c09-96EE-FD53A6805CFD}" DisplayName="Description" Required="TRUE" Type="Note" ShowInNewForm="TRUE"/> 
    <Field Name="MovieID" ID="{3DA59372-F977-477c-B4FC-7DF2BF673295}" DisplayName="MovieID" Required ="TRUE" Type="Text" ShowInNewForm="TRUE"/> 
    <Field Name="Preview" ID="{0BDE7F01-B6EC-49fd-8890-F5E7050DFFA0}" DisplayName="Previsualisation" Type="Computed" ShowInNewForm="FALSE"> 
    <FieldRefs> 
    <FieldRef Name=" MovieID"/> 
    </FieldRefs> 
    <DisplayPattern> 
    <HTML><![CDATA[<object width="425" height="355"><param name="movie" value="http://www.youtube.com/v/]]></HTML> 
    <Column Name=" MovieID"/> 
    <HTML> 
    <![CDATA[&rel=1"> 
    </param> 
    <param name="wmode" value="transparent"></param> 
    <embed src="http://www.youtube.com/v/]]> 
    </HTML> 
    <Column Name=" MovieID"/> 
    <HTML> 
    <![CDATA[&rel=1" type="application/x-shockwave-flash" wmode="transparent" width="425" height="355"> 
    </embed></object>]]> 
    </HTML> 
    </DisplayPattern> 
    </Field> 
    </Fields>

    Attention les ID des champs doivent être les mêmes que les id définis dans le ContentType. Remarque le champ preview, ce champ est de type computed, en fait il affichera le code HTML faisant une instance du flash en utilisant l’id de la vidéo fournit par la propriété MovieID.

    Les colonnes sont définies. Cependant elles ne sont pas encore affichées dans les vues. Le code en dessous de la définition des champs définit les vues. On va donc ajouter l’affichage de nos champs dans les vues.

    On a deux vues de définies, la première est la vue par défaut, la deuxième est la vue de la liste allitems. On va donc ajouter nos champs dans l’affichage des deux vues. Pour cela on repère le nœud ViewFields, il se situe à deux endroits, un pour chaque vue, le premier est a peu prés au milieu du fichier et le deuxième est à la fin.

    Voici le code qui le remplace :

    <ViewFields> 
    <FieldRef Name="LinkTitleNoMenu"/> 
    <FieldRef Name="Description"/> 
    <FieldRef Name="MovieID"/> 
    <FieldRef Name="Preview"/> 
    </ViewFields>

    Une fois que l’on a fait cela on peut déployer notre feature. Pour cela, soit on fait F5, ce qui n’a que peu d’intérêt ici car on n’a pas de code réel. Soit on fait un clic avec le bouton droit de la souris sur le projet et on sélectionne deploy. La plupart des erreurs se passeront au déploiement, car c’est qu’a ce moment la que SharePoint vérifie la syntaxe et l’exactitude des informations fournies dans le XML.

    La feature déployé, il nous reste plus qu’a tester. Etant donné que l’on a demandé la création d’une instance de la liste, on n’a pas besoin d’en créer une, elle est créée automatiquement. Dans View all site content on a YoutubeList Instance:

    On clique dessus et on sélectionne new :

    Vous pouvez remarquer que les champs sont obligatoires. Ceci est du au fait qu’on a mis Required=«true» dans la déclaration de la liste.

    Une fois notre item créé on peut le voir dans la liste avec la vidéo :

    Et si on sélectionne un item on voit la vidéo s’afficher aussi :

    L’avantage de faire une liste est qu’on peut modifier l’affichage, trier ou regrouper les items. Par exemple si on ajoute une colonne catégorie a notre liste et qu’on créé une vue de regroupement on obtient :

    On peut donc vraiment utiliser toute la puissance des listes.

    Conclusion

    Beaucoup de développement SharePoint se basent uniquement sur les WebParts. Alors que le framework SharePoint est beaucoup poussé. La création de liste est vraiment l’un des points les plus puissants de SharePoint car on reste proche de la philosophie de SharePoint

     
    							
  • Viewport2DVisual3D: Enter the 3D Matrix expander

    Afin d'illustrer une des plus importantes innovations apportées par WPF 3.5, je remets au goût du jour un contrôle que j'avais réalisé avec la CTP de février 2005: le contrôle MatrixExpander. A cette occasion, le contrôle aura subit des améliorations de conception. C'est toujours un dérivé du contrôle Expander intégrant une interface audio et vidéo, mais cette fois les media sont compilés avec le source et le contrôle sera en 3D full interactif.

    Enter the matrix expander

     

    Introduction

    WPF 3.5 améliore la richesse du Viewport3D par l'ajout de la classe Viewport2DVisual3D qui permet de rendre n'importe quel visuel 2D totalement interactif en tant que modèle 3D. Autrefois appliqué comme texture à un modèle 3D, le contrôle reste totalement opérationnel. J'ai alors considéré la conception du contrôle de manière indépendante, tel qu'il sera utilisé dans n'importe quelle application puis je lui rajoute une décoration 3D que je façonne selon le résultat final à obtenir. Le contrôle n'a par conséquent pas besoin d'être conçu dans la perspective d'être appliqué sur un modèle 3D.

    Custom Expander

    Le contrôle dérive de la classe de base Expander et encapsule le template pour fournir le visuel. Un contrôle ToggleButton templaté est utilisé pour être bindé à la propriété IsExpanded de l'Expander.

    <Expander.Template> 
        <ControlTemplate TargetType="{x:Type lc:RDExpander}"> 
          <Grid x:Name="EnterTheMatrix" Width="168" Height="210"> 
            <Grid> 
              <Grid x:Name="_expanderContent" Panel.ZIndex="0"> 
                <Image Source="../Images/MatrixFrame.png" Width="160" Height="101" Stretch="Uniform" StretchDirection="Both" /> 
                <ContentPresenter /> 
                <Grid.RenderTransform> 
                  <TranslateTransform X="0" Y="0" /> 
                </Grid.RenderTransform> 
              </Grid> 
              <Image x:Name="PART_MatrixBg" Width="168" Height="109" Stretch="Uniform" StretchDirection="Both" Source="../Images/MatrixScreen.png" Panel.ZIndex="10" VerticalAlignment="Top" /> 
              <Border x:Name="PART_Border" Width="168" Height="101" Background="Transparent" Panel.ZIndex="-10" VerticalAlignment="Bottom" /> 
            </Grid> 
            <ContentPresenter x:Name="_header" ContentSource="Header" /> 
            <ToggleButton x:Name="_matrix" IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"> 
              <ToggleButton.Template> 
                <ControlTemplate> 
                  <Grid> 
                    <MediaElement x:Name="MatrixMovie" Width="135" Height="77" Stretch="UniformToFill" Opacity="0"> 
                      <MediaElement.Triggers> 
                        <EventTrigger RoutedEvent="MediaElement.MediaOpened"> 
                          <EventTrigger.Actions> 
                            <BeginStoryboard> 
                              <Storyboard> 
                                <DoubleAnimation Duration="0:0:0.5" To="1" Storyboard.TargetProperty="Opacity" /> 
                              </Storyboard> 
                            </BeginStoryboard> 
                          </EventTrigger.Actions> 
                        </EventTrigger> 
                      </MediaElement.Triggers> 
                    </MediaElement> 
                  </Grid> 
                  <ControlTemplate.Triggers> 
                    <EventTrigger RoutedEvent="FrameworkElement.Loaded"> 
                      <EventTrigger.Actions> 
                        <BeginStoryboard> 
                          <Storyboard> 
                            <MediaTimeline Source="{local:EmbeddedMedia Source='pack://application:,,,/FX/matrix.avi'}" RepeatBehavior="Forever" Storyboard.TargetName="MatrixMovie" /> 
                          </Storyboard> 
                        </BeginStoryboard> 
                      </EventTrigger.Actions> 
                    </EventTrigger> 
                  </ControlTemplate.Triggers> 
                </ControlTemplate> 
              </ToggleButton.Template> 
            </ToggleButton> 
          </Grid>

    La difficulté est de fournir le comportement adéquat selon la direction de l'expander. Dans le cas précis, je décide de ne fournir que les comportements pour les directions Down et Up. Les autres sont assimilées à la direction Down. Il semble que plusieurs méthodes soient possibles pour animer le comportement de l'expander. Pour ma part, j'override la méthode OnExpanded et exécute les Storyboards en conséquence.

    protected override void OnExpanded()
        {
          base.OnExpanded();
          Grid __expanderContent = (Grid)base.GetTemplateChild("_expanderContent");
          switch (base.ExpandDirection)
          {
            case ExpandDirection.Up:
              __expanderContent.BeginStoryboard((Storyboard)this.Resources["Expanded_Up"]);
              break;
    
            case ExpandDirection.Down:
            case ExpandDirection.Left:
            case ExpandDirection.Right:
            default:
              __expanderContent.BeginStoryboard((Storyboard)this.Resources["Expanded_Down"]);
              break;
          }
        }

    Une autre nouveauté réside dans l'intégration des fichiers audio et vidéo dans l'application. Effectivement, le contrôle MediaElement n'accepte toujours pas des URI pointant vers des ressources embedded. Un MarkupExtension nommé EmbeddedMedia permettra de combler cette lacune. La seule contrainte est la création d'un dossier caché afin de "descendre" les fichiers sur le disque avant de les charger (merci à Avalonboy pour sa contribution).

    <MediaTimeline Source="{local:EmbeddedMedia Source='pack://application:,,,/FX/matrix.avi'}" RepeatBehavior="Forever" Storyboard.TargetName="MatrixMovie" />
    public sealed class EmbeddedMediaExtension : MarkupExtension
      {
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
          StreamResourceInfo __resourceStream = Application.GetResourceStream(this._source);
          DirectoryInfo __directoryInfo = new DirectoryInfo(string.Format("{0}\\{1}", Directory.GetCurrentDirectory(), CACHE_FOLDER));
    
          if (!__directoryInfo.Exists)
          {
            __directoryInfo.Create();
            __directoryInfo.Attributes = FileAttributes.Hidden;
          }
    
          FileInfo __fileInfo = new FileInfo(string.Format("{0}/{1}", __directoryInfo.FullName, this._source.Segments[this._source.Segments.Length - 1]));
          if (!_cache.ContainsKey(this._source))
          {
            if (!__fileInfo.Exists)
            {
              int __byte;
              FileStream stream = new FileStream(__fileInfo.FullName, FileMode.CreateNew);
              while ((__byte = __resourceStream.Stream.ReadByte()) != -1)
              {
                stream.WriteByte((byte)__byte);
              }
              stream.Close();
              __resourceStream.Stream.Close();
            }
            _cache.Add(this._source, __fileInfo);
            return new Uri(__fileInfo.FullName);
          }
          return new Uri(_cache[this._source].FullName);
        }
      }

    Décoration 3D

    Les contrôles sont prêts à être consommés par l'application tant en visuel 2D qu'en 3D. La classe Viewport2DVisual3D va jouer son rôle de décorateur 3D pour les contrôles Expander. Son utilisation est simple puisqu'elle s'ajoute comme élément enfant du contrôle Viewport3D tel qu'un Model3D classique. Il suffit de définir la Geometry3D à utiliser, dans mon cas une forme de type Plane sera suffisant, de définir le contrôle en tant qu'élément visuel et de spécifier que la texture à utiliser est un élément interactif.

    <Viewport3D ClipToBounds="False" Width="168" Height="210">
              <Viewport3D.Camera>
                <PerspectiveCamera FarPlaneDistance="10" LookDirection="0,0,-1" UpDirection="0,1,0" NearPlaneDistance="1" Position="0,0,1.9" FieldOfView="45" />
              </Viewport3D.Camera>
    
              <Viewport2DVisual3D>
                <Viewport2DVisual3D.Geometry>
                  <MeshGeometry3D Positions="-1,1,0 -1,-1,0 1,-1,0 1,1,0" TextureCoordinates="0,0 0,1 1,1 1,0" TriangleIndices="0,1,2 2,3,0" />
                </Viewport2DVisual3D.Geometry>
    
                <Viewport2DVisual3D.Material>
                  <DiffuseMaterial Viewport2DVisual3D.IsVisualHostMaterial="True" />
                </Viewport2DVisual3D.Material>
    
                <Viewport2DVisual3D.Transform>
                  <Transform3DGroup>
                    <ScaleTransform3D ScaleX="0.8" ScaleY="1" ScaleZ="1" />
                    <RotateTransform3D>
                      <RotateTransform3D.Rotation>
                        <AxisAngleRotation3D Angle="18" Axis="1 0 0"/>
                      </RotateTransform3D.Rotation>
                    </RotateTransform3D>
                    <TranslateTransform3D OffsetX="-0.25" OffsetY="-0.045" OffsetZ="0.24" />
                  </Transform3DGroup>
                </Viewport2DVisual3D.Transform>
    
                <lc:RDExpander Margin="2" ExpandDirection="Up" MouseEnter="_ExpanderEnter">
                  <lc:RDExpander.Header>
                    <lc:MatrixTextBlock Text="Enter Thor's matrix" ImmediateWrite="False" Width="128" Height="Auto" TextAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center" TextWrapping="WrapWithOverflow" />
                  </lc:RDExpander.Header>
                  <Grid>
                    <StackPanel Width="130" Height="70">
                      <TextBlock Margin="2">
                                          <Hyperlink NavigateUri="http://blog.rioterdecker.net/blogs/valhalla">Thor's blog</Hyperlink>
                                      </TextBlock>
                      <TextBlock Margin="2">
                                          <Hyperlink NavigateUri="http://www.netfxfactory.org">NetFXFactory</Hyperlink>
                                      </TextBlock>
                      <TextBlock Margin="2">
                                          <Hyperlink NavigateUri="http://www.mexedge.com">MexEdge</Hyperlink>
                                      </TextBlock>
                    </StackPanel>
                  </Grid>
                </lc:RDExpander>
              </Viewport2DVisual3D>
    
              <ModelVisual3D>
                <ModelVisual3D.Content>
                  <Model3DGroup>
                    <AmbientLight Color="#333333" />
                    <DirectionalLight Color="#FFFFFF" Direction="-0.612372,-0.5,-0.612372" />
                    <DirectionalLight Color="#FFFFFF" Direction="0.612372,-0.5,-0.612372" />
                  </Model3DGroup>
                </ModelVisual3D.Content>
              </ModelVisual3D>
            </Viewport3D>

    Le projet est attaché ainsi que le binaire.
    J'adore WPF !! Une seule limite...celle de votre imagination !!

  • Vista Thumbnail live en 3D

    Introduction

    Les thumbnail de vista sont, les miniatures qui représente les application en train de fonctionner sur le systéme d'exploitation. Un exemple de thumbnail est le Flip 3D, qui affiche les application en 3D et les fait defiler. Jusque maintenant pour récupérer les thumbnail dans nos applications, on avait que la possibilité de le faire en 2D, j'ai d'ailleur écrit un article dessus.
    http://netfxfactory.org/blogs/articles/archive/2007/03/30/dwm-api-et-milcore.aspx

    Jeremiah Morril a reussi a trouver comment récupérer les Live thumbnail pour en faire des texture Directx. Voici comment il a procédé.

    Dans Windows Visa, chaque fenêtre est un objet 3D qui est géré par DirectX. Le bureau est entiérement gérer par DwmApi qui est une api de plus haut niveau que l'api MilCore (Api qui gére d'ailleur le WPF).

    Les méthodes cachées

    Les méthodes cachées qui permettent de récupérer les textures, sont des méthodes cachées et non documenté de DWMAPI: DwmpDxGetWindowSharedSurface et DwmpDxUpdateWindowSharedSurface.

    Leur signature est la suivante.

    typedef
    
    int (__stdcall *PFNDWM_DXGETWINDOWSHAREDSURFACE)(HWND hWnd, LUID adapterLuid, DWORD one, DWORD two, DWORD *pD3DFormat, HANDLE *pSharedHandle, unsigned __int64 *arg7); 
    typedef
    
    int (__stdcall *PFNDWM_DwmpDxUpdateWindowSharedSurface)(HWND hWnd, int, int, int, HMONITOR hMonitor, void* test); 

    Chargement des fonctions

    Bien entendu comme les fonctions ne sont pas documentées, on ne peut pas les récupérer à partir d'un simple fichier H à inclure, voici donc comment on les chargera:

    HMODULE hDwmApi = LoadLibrary(L"dwmapi.dll"); 
    pDwmpDxGetWindowSharedSurface = (PFNDWM_DXGETWINDOWSHAREDSURFACE)GetProcAddress(hDwmApi, (LPCSTR)100);
    pDwmpDxUpdateWindowSharedSurface = (PFNDWM_DwmpDxUpdateWindowSharedSurface)GetProcAddress(hDwmApi, (LPCSTR)101);
    

    Récupérer les SharedTexture

    Maintenant qu'on a les fonction on peut les utiliser pour récupérer les "textures" des fenêtres:

    hMonitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);
    monitorInfo.cbSize =sizeof(monitorInfo); 
    GetMonitorInfo(hMonitor, &monitorInfo);
    
    memcpy(&openDev.DeviceName, monitorInfo.szDevice, 32);
    HRESULT hr;
    hr = pOpenAdapterFromGdiDisplayName(&openDev);
    adapterLuid = openDev.AdapterLuid;
    closeDev.hAdapter = openDev.hAdapter;
    hr = pCloseAdapter(&closeDev);
    hr = pDwmpDxGetWindowSharedSurface(hWnd, adapterLuid, 0, 0, d3dFormat, sharedHandle, &unknown);
    
    

    Dans ce code on utilise pDwmpDxGetWindowSharedSurface auquel on passe le Handle de fenêtre dont on veut récupérer la texture, cette fonction retournera un pointeur sur la texture a utiliser

    Mettre la Shared Texture dans une texture DirectX 3D

    voici le bout de code qui permet de le récupérer:

    HANDLE texHandle = NULL;
    
    DWORD fmt = 0;
    
    RECT winRect;
    
    HWND h;
    
    h = FindWindow(NULL, L"directx or wpf application"); 
    
    GetWindowRect(h, &winRect); 
    
    HRESULT res;
    
    SIZE sWin = { winRect.right - winRect.left, winRect.bottom - winRect.top };
    
    GetWindowSharedSurfaceHandle(h, &texHandle, &fmt); //
    
    IDirect3DTexture9 *texture;
    
    hr = g_pd3dDevice->CreateTexture(sWin.cx, sWin.cy, 1, D3DUSAGE_RENDERTARGET, (D3DFORMAT)fmt, D3DPOOL_DEFAULT, &g_pTexture, &sharedHandle);
    
    pDwmpDxUpdateWindowSharedSurface(h, 0, 0, 0, 0, 0);

    En piéce jointe il y a le code de textures.cpp, ce code prendra la place de Textures.cpp qui est le tutorial 5 du SDK DirectX. C'est ce même code qui est utilisé pour la vidéo présente sur le site de Jeremiah:

    http://jmorrill.hjtcentral.com/Portals/21/Blog/Files/14/111/dwmhack.zip

    il suffit de modifier la ligne 380 avec le titre de la fenêtre que vous voulez monitorer. Pour l'instant seul les fenêtre utilisant Directx ou WPF peuvent être utiliser en tant que texture mais tres vite les fenêtre GDI en feront partie aussi

    Merci beaucoup a jeremiah pour avoir partager l'information et m'avoir permis d'en parler si librement.

  • WF et Biztalk 2006 R2

    Deux Webcast illustrant l'article sont disponibles :
    http://www.netfxfactory.org/blogs/video___la_demande/archive/2007/07/04/n3rd-s4-composer-son-portefeuille-d-activit-m-tier-avec-wf-le-premier-pas-vers-le-management-des-processus.aspx
    et a la fin de l'article

    Introduction

    Business activity monitoring ou supervision des activité métier est une fonctionnalité de biztalk permettant de superviser et de voir les performances du déroulement d'un processus métier. Cela permet de savoir les temps d'exécution du processus et de tracer, par exemple, les temps d'attente ou les goulets d'étranglement dans l'execution d'un processus. En effet BAM se base sur un mapping entre l'exécution d'une orchestration Biztalk et un fichier Excel qui contient les données, les dimensions et les mesures du processus métiers. Avec la version 2006 R2 de biztalk on voit apparaitre le BamTrackingService. Celui ci permet d'envoyer à BAM les informations concernant l'exécution non pas d'une orchestration Biztalk, mais d'un Workflow WF. Dans cet article nous allons un peu detourner le comportement de BAM pour tracer non pas les performances ou les temps d'attentes d'un processus mais plutot compter le nombre d'occurence de la valeur d'une variable dans le processus métier.

    Application Exemple

    L'application exemple est un simple hello world. Voici le résultat d'execution du workflow:

    image

    Ce workflow demande le nom, prenom et le sexe de la personne qui utilise le workflow. Le workflow en lui même est le suivant:

    image

    Dans notre exemple on va compter les genres de personnes qui utiliseront le workflow (fille, garçon et erreur) l'erreur sera utilisé en cas de faute de frappe par exemple.

    BamTrackingService

    Comme son nom l'indique, le BamTrackingService est un service WF. Il s'utilise très simplement dans le sens ou son implémentation est identique à celle d'un autre service comme le tracking ou la persistence. Pour cela on créer une instance de BamTrackingService auquel on ajoute la chaine de connexion à la base de données, ainsi que l'intervalle de pooling:

    System.Collections.Specialized.NameValueCollection parameters = new System.Collections.Specialized.NameValueCollection(); 
    parameters.Add("PollingIntervalSec", "5"); 
    parameters.Add("ConnectionString", "Integrated Security=SSPI;Data Source=.;Initial Catalog=BAMPrimaryImport");
    workflowRuntime.AddService(new BamTrackingService(parameters)); 

     

    Voila pour le workflow il n'y a rien d'autre a faire. Maintenant on va paramétrer BAM pour qu'il puisse tracer notre workflow.

     

    BamActivity

    Premiérement on va paramétrer BAM en utilisant le plugin BAM pour excel. Pour cela on lance Excel sur le serveur Biztalk et on va crée une activity bam et une vue pour afficher les données. On lance l'assistant de création d'activité Bam d'excel:

    image

    On créé une activité auquel on ajoute un item Gender qui récuperera les données venant du workflow:

    image

    image

    Une fois l'activité créé l'assistant de création de vue se lance a son tour:

    image

    On créé une vue en adéquation avec l'activité qu'on vient de créer puis on ajoute les items de l'activité et les alias sur les items:

    image

    image

    Bien maintenant on créé la mesure et la dimension qui permettront de compter le nombre de garçon, de fille ou les erreurs:

    image

    Voici la mesure:

    image

    et voici la dimension:

    image

    Maintenant qu'on a les données et les mesures on va les insérer dans la pivot table:

    image

    Le principe est le même qu'un cube OLAP.

    Pour l'exemple je vais utiliser des données LIVE pour cela j'active le bouton RTA (Real Time Aggregation):

    image
    Quand on a finit notre tableau avec nos données on peut le déployer sur le serveur avec l'outil BM:

    image

    BM fait deux choses quand on l'utilise avec un fichier XLS, la premiére c'est de déployer l'activité et la vue sur le serveur, puis il créé un fichier live qu'on utilisera plus tard.

    BamConfiguration

    C'est la partie la plus complexe du processus. Non pas que le principe soit complexe, mais, pour une raison que j'ignore, il n'y a pas d'assistant ou d'aide à la création du fichier qui fera la relation entre la vue et l'activité déployé sur le serveur et le workflow.

    Voici le code complet du fichier:

    <?xml version="1.0" encoding="utf-8" ?> 
    <ic:InterceptorConfiguration xmlns:ic="http://schemas.microsoft.com/BizTalkServer/2004/10/BAM/InterceptorConfiguration xmlns:wf="http://schemas.microsoft.com/BizTalkServer/2004/10/BAM/WorkflowInterceptorConfiguration> 
        <ic:EventSource Technology="WF" Name="Workflow1" Manifest="HelloWorldWorkflowLib.Workflow1, HelloWorldWorkflowLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>    
            <ic:BamActivity Name="HelloWorldActivity"> 
                <ic:OnEvent Name="HelloWorldEvent" Source="Workflow1" IsBegin="true" IsEnd="true"> 
                    <ic:Filter> 
                        <ic:Expression> 
                            <wf:Operation Name="GetActivityName" /> 
                            <ic:Operation Name="Constant"> 
                                <ic:Argument>AskGender</ic:Argument> 
                            </ic:Operation> 
                            <ic:Operation Name="Equals" /> 
                            <wf:Operation Name="GetActivityEvent" /> 
                            <ic:Operation Name="Constant"> 
                                <ic:Argument>Closed</ic:Argument> 
                            </ic:Operation> 
                            <ic:Operation Name="Equals" /> 
                            <ic:Operation Name="And" /> 
                        </ic:Expression> 
                    </ic:Filter> 
                    <ic:CorrelationID> 
                        <ic:Expression> 
                            <wf:Operation Name="GetContextProperty"> 
                                <wf:Argument>EventTime</wf:Argument> 
                            </wf:Operation> 
                        </ic:Expression> 
                    </ic:CorrelationID> 
                    <ic:Update DataItemName="Gender" Type="NVARCHAR"> 
                        <ic:Expression> 
                            <wf:Operation Name="GetWorkflowProperty"> 
                                <wf:Argument>Gender</wf:Argument> 
                            </wf:Operation> 
                        </ic:Expression> 
                    </ic:Update> 
                </ic:OnEvent> 
            </ic:BamActivity>      
    </ic:InterceptorConfiguration> 
    
    

     

    Regardons comment il fonctionne:

    Premiérement on créé la source des événements. La source des événements est le manifest de la classe du Workflow. On donne un nom à l'event source, par principe on utilise le nom du workflow ici Name="Workflow1" mais on pourrait mettre ce qu'on veut. Ensuite fait le mapping avec l'activité créé par le fichier Excel, ici HelloWorldActivity.

    On base le mapping sur un événement du Workflow, l'événement que j'ai choisit et la sortie de l'activité AskGender qui demande le sexe et le mets dans la propriété Gender.

    Voici le bout de code qui permet de faire cela:

    <ic:OnEvent Name="HelloWorldEvent" Source="Workflow1" IsBegin="true" IsEnd="true">
    

    Ce code permet de crée l'événement par rapport a la source définit plus haut. Ensuite on indique que cette événement sera le début et la fin du tracking. En fait comme on en a pas d'autre le tracking commencera et finira sur cette événement.

    <ic:Filter> 
                        <ic:Expression> 
                            <wf:Operation Name="GetActivityName" /> 
                            <ic:Operation Name="Constant"> 
                                <ic:Argument>AskGender</ic:Argument> 
                            </ic:Operation> 
                            <ic:Operation Name="Equals" /> 
                            <wf:Operation Name="GetActivityEvent" /> 
                            <ic:Operation Name="Constant"> 
                                <ic:Argument>Closed</ic:Argument> 
                            </ic:Operation> 
                            <ic:Operation Name="Equals" /> 
                            <ic:Operation Name="And" /> 
                        </ic:Expression> 
                    </ic:Filter> 
    
    

    Ici on filtre les événements déclenché par le workflow. On récupére l'événement sur L'activité AskGender qu'on récupére grace à GetActivityName. Ensuite on filtre sur la fermeture de l'activité qui correspond a la sortie de celle ci. Pour cela on utilise GetActivityEvent et on spécifie Closed. Le filtre est "quand l'activité est égal à AskGender ET l'évenement est égal à Closed. Les opérations permette de mettre en place les test d'égalité et l'opérateur AND.

    <ic:CorrelationID> 
                        <ic:Expression> 
                            <wf:Operation Name="GetContextProperty"> 
                                <wf:Argument>EventTime</wf:Argument> 
                            </wf:Operation> 
                        </ic:Expression> 
                    </ic:CorrelationID> 
    

    La correlation permet de regrouper les événements. Ici on utilisera le temps car on ne veut pas de regroupement mais dans la plupart des cas on utilisera IntanceID.

    <ic:Update DataItemName="Gender" Type="NVARCHAR"> 
                        <ic:Expression> 
                            <wf:Operation Name="GetWorkflowProperty"> 
                                <wf:Argument>Gender</wf:Argument> 
                            </wf:Operation> 
                        </ic:Expression> 
                    </ic:Update>
    

    Ici on indique ce que l'interceptor doit faire, en l'occurence mettre à jour la propriété Gender avec la valeur de la variable Gender du workflow.

    Maintenant on a plus qu'à déployer notre fichier de configuration, toujours avec l'utilitaire BM:

    image

     

    Execution

    Bien passons à l'éxécution. Pour cela on va exécuter plusieur fois le Workflow pour remplir la base de données BAM:

    image 

    On va commencer par ouvrir le fichier Excel live qui a été créé par le déploiement du fichier Excel de configuration de BAM:

    image

    Comme vous pouvez le voir on a le décompte des informations entré dans le workflow.

    L'avantage de BAM c'est le portail qui se base sur Sharepoint (2003 pour l'instant)

    image

    Dans ce portail on a les même information que dans le fichier Excel.

    Conclusion

    BAM est un tres gros avantage de Biztalk 2006 R2, il permet de facilement tracer l'exécution du processus métier. Le fait qu'il soit possible d'integrer WF avec offre un gros avantage a WF. La question qui se pose est quand est ce que Microsoft vendra BAM sans Biztalk ;)).

    Format: wmv
    Duration: --:--

  • Article d'introduction à Windows Workflow Foundation (WF)

    Ce document vous propose de parcourir les différentes fonctionnalités de la technologie Windows Workflow Foundation (WF). Ce parcours s'appuie sur la mise en pratique d'un exemple simple.

    Au fil de la lecture de ce document, vous appréhenderez la notion de workflow séquentiel, vous la comparerez à celle de machine à états. Vous saurez comment échanger des données avec vos workflows mais également personnaliser ces derniers via le développement de vos propres activités.
    Ce document abordera en conclusion des premiers services offert par WF tel que le service de tracking et de persistence.
    Ce document est le premier d'une série d'articles ou de webcasts qui vous emmeneront dans des voyages thématiques autour de la technologie WF

    Cliquez pour le télécharger

  • Mise en perspective des patterns de sécurité offerts par WCF

    WCF représente l’un de mes principaux sujets d’investigation. Et la richesse des possibilités offertes est telle qu’il m’a paru opportun de partager certaines des expériences acquises au cours de mes différentes mises en œuvre de cette technologie.

    Les services de Web permettent aux applications d’interopérer sur différentes plateformes. Cependant, les technologies existantes ne satisfont pas toujours aux exigences de sécurité et de fiabilité pour les communications d’entreprise. WCF (« Windows Communication Foundation »), framework unifié pour la construction d’applications orientées services, y répond, en fournissant un modèle de programmation unifié pour construire des applications sécurisées, fiables et interopérables sur la base des spécifications WS-*. L'objet de ce document est de présenter les patterns offerts par WCF pour la sécurisation de l’échange des messages et la sécurité d’accès aux ressources.

    La mise en perspective des patterns de sécurité offerts par WCF suppose la mise en place d’une démarche de présentation appropriée. Tout au long de ce document, je me suis donc attaché à respecter un « mode opératoire » fondé sur le rappel des concepts associés à la sécurité des échanges de messages, sur l’identification des patterns et des normes de sécurité, et sur l’application et la composition de ces patterns avec WCF.

    Les fonctions de sécurité de WCF sont implémentées dans 2 couches : « Channel » (authentification et transfert contrôlés via les « bindings »), « Service Model » (autorisations paramétrées via les « behaviors »). L'ensemble de caractéristiques de programmation et de configuration associées à ces fonctions sont analysées et présentées avec soin. Sont notamment abordés le choix des différents modes de sécurité (transport et message), les mécanismes de protection des messages (chiffrement et signature), les différents modes d’authentification, l'emprunt d’identité, la délégation, le contrôle d’accès fondé sur les rôles, le contrôle d’accès fondé sur l’usage de claims, la fédération d’identité, « Cardspace », l'audit... Autant d'occasions de découvrir la flexibilité des mécanismes de développement et de déploiement des services web avancés, avec une nette séparation entre l’abstraction de la logique « métier » et le paramétrage des détails de la communication, garantissant de surcroît l'extensibilité de la solution grâce au support de l’évolution des normes WS-*.

    Téléchargez la version complète de cet article à cette adresse.
  • WPF 3D

    J'ai publié un article sur la 3D dans WPF vous pouvez le télécharger ici, voici le TOC:

    1. Table des matières. 2
    2. Introduction. 5
    3. La 3D.. 6
      1. L’origine de la 3D.. 6
    4. Ma première scène. 7
    5. La fenêtre. 9
    6. Le Viewport3D.. 10
      1. Le bitmap Effect 10
        1. Emboss. 10
        2. Bevel 10
        3. Blur. 11
        4. Glow.. 12
    7. Les caméras. 13
      1. Perspective Camera. 13
      2. L’orthographique Caméra. 22
    8. Les lumières. 26
      1. La directional light 26
      2. La spot Light 29
      3. L’AmbientLight 32
      4. La PointLight 33
      5. QuadratiqueAttenuation, LinearAttenutation et ConstantAttenuation. 34
    9. Les maillages. 36
      1. Les faces et les points. 37
      2. De l’importance du nombre de faces. 43
      3. Les normales et la lumière. 46
      4. TriangleIndices. 50
    10. Les textures. 52
      1. Les coordonnées de texture. 53
        1. Et une texture multiface ca existe ?. 55
      2. Les brosses. 56
        1. SolidColorBrush. 56
        2. LinearGradientBrush. 58
        3. RadialGradientBrush. 59
        4. ImageBrush. 59
        5. DrawingBrush. 61
        6. VisualBrush. 62
      3. Les types de matériaux. 64
        1. Diffuse. 64
        2. Specular. 66
        3. Emissive. 67
    11. Les transformations. 69
      1. Translation. 69
      2. Rotation. 70
        1. Axis. 74
        2. Quaternion. 76
      3. Mise à l’échelle. 77
      4. Transformation Matricielle. 78
    12. 3D Toolkit for WPF. 82
      1. Code to interact with 2D content placed on 3D.. 82
      2. Trackball class to rotate the camera using the mouse. 84
    13. Boite à outils indispensable. 86
    14. Conclusion. 87
    15. Lien important 87
      1. WPF. 87
      2. Math3D.. 87
  • IIS module

    IIS 7

    Code source de l'article

    Introduction

    L’une des plus grosses différences dans l’architecture APACHE par rapport à IIS, c’est qu’Apache est basé sur des modules. En effet, la plupart des plugins pour IIS sont développés en ISAPI mais ne font pas réellement partie du serveur, alors que la modularité d’apache lui permet une grosse souplesse et un certain confort pour les hébergeurs. De plus il est assez complexe de développer des ISAPI car une certaine connaissance du C++ est absolument nécessaire étant donné que les ISAPI doivent être développé en C++. Avec IIS 7 les choses changent énormément. En effet IIS 7 est entièrement modulaire, et les modules peuvent être développés en .Net. On peut ainsi les ajoutés et les supprimés du serveur et ce directement dans le fichier Web.Config. Dans cet article, nous verrons comment développer un module qui listera un répertoire d’images et en fera une gallérie, ce qui permettra d’avoir juste a uploader l’image dans le répertoire pour être prise en compte par la gallérie. Une sorte de directory browsing amélioré fait pour les images.

    Mon Premier module.

    Dans ce développement on lira les fichiers qui sont dans le répertoire demandé et on affichera les images sous dans une galerie, ce qui sera toujours plus beau que la forme actuelle :

    Actuellement on a ceci :

    C’est les nouvelles erreurs d’IIS 7. En fait la directory browsing n’est pas activé ni autorisé sur le répertoire. On va donc l’activé. On va dans l’explorateur IIS 7 pour pouvoir l’activer. L’interface d’administration d’IIS se trouve dans les outils d’administrations dans le panneau de configuration. On a une interface un peu différente d’IIS 6 (en fait elle est radicalement différente):

    Et oui finit le clic droit/propriétés, maintenant il faut activer les modules, pour activer des fonctionnalités. Pour cela on sélectionne « exploration de répertoire » ou « directory browsing » en anglais et on clic sur activer :

    Comme vous pouvez le voir les modules sont paramétrable. Pour ce directory browsing on peu spécifier ce qui va être affiché.

    Voici ce qui apparait maintenant quand on demande le répertoire.

    Un Web.Config a été créé. Ce Web.Config possède le code suivant :

    <?xml version="1.0" encoding="UTF-8"?>
    
    <configuration>
    
    <system.webServer>
    
    <directoryBrowse enabled="true" />
    
    </system.webServer>
    
    </configuration>
    
    

    Comme vous pouvez le voir un nœud qui se nomme system.webServer permet de configurer le serveur IIS 7 directement dans le fichier de configuration, c’est d’ailleurs ce que l’interface d’administration d’IIS 7 fait quand on active ou paramètre des modules. C’est dans le web.Config que l’on fera le déploiement de notre module plus tard. Mais commençons le développement. Première chose à faire, c’est télécharger le IIS 7 Manages module sur le site IIS.NET à l’adresse suivante http://www.iis.net/downloads/default.aspx?tabid=34&g=6&i=1302

    Apres l’avoir installé, je lance visual studio et je créé mon projet de type IIS 7 managed module :

    Lorsque j’ai fait cela le template de projet m’a automatiquement créé une classe qui hérite de IHttpModule, un fichier readme qui me permet de savoir ce que je dois faire pour créer un module et un fichier Web.config pour ajouter le module à mon site.

    Le code de mon module sera relativement simple. On va commencer par gérer l’événement BeginRequest de l’objet HttpApplication. On va donc ajouter ceci dans la fonction Init :

    application.BeginRequest += new EventHandler(application_BeginRequest);

    Ce code doit vous êtes très compréhensible, c’est une gestion courante d’événement. Ensuite dans le OnBeginRequeste on va recupérer l’application courante, le contexte, l’objet request et response:

    void application_BeginRequest(object sender, EventArgs e)
    
    {
    
    HttpApplication objApplication = (HttpApplication)sender;
    
    HttpContext objContext = objApplication.Context;
    
    HttpRequest objRequest = objContext.Request;
    
    HttpResponse objResponse = objContext.Response;
    
    }
    
    

    L’objet Resquest nous permettra de savoir le chemin du répertoire demandé et l’objet response nous permettra de renvoyer la page HTML formatée avec les images.

    Bien Maintenant je vais mettre en forme toutes les images contenues dans mon répertoire:

    Premiérement on récupére les informations du répertoire avec un DirectoryInfo :

     

    DirectoryInfo objDirInfo = new DirectoryInfo(objRequest.PhysicalPath);

    Ensuite on boucle sur le répertoire pour récupérer les informations des images et les afficher:

    if (objDirInfo.Exists)
    
    { 
    
    foreach(FileInfo objFile in objDirInfo.GetFiles())
    
    {
    
    if (objFile.Extension == ".png" || objFile.Extension == ".jpg" || objFile.Extension == ".gif")
    
    {
    
    objResponse.Write("<a href='" + objFile.Name + "'><img src='" + objFile.Name + "' width=100></a>");
    
    }
    
    }
    
    }
    
    

    Ceci est assez basique on peut formatter la page pour quelle soit plus belle, voici par exemple une page avec une plus belle mise en forme :

     

    void application_BeginRequest(object sender, EventArgs e)
    
    {
    
    HttpApplication objApplication = (HttpApplication)sender;
    
    HttpContext objContext = objApplication.Context;
    
    HttpRequest objRequest = objContext.Request;
    
    HttpResponse objResponse = objContext.Response;
    
    DirectoryInfo objDirInfo = new DirectoryInfo(objRequest.PhysicalPath);
    
    StringBuilder strBuild = new StringBuilder();
    
    strBuild.Append("<html><head><title>");
    
    strBuild.Append(objRequest.Path);
    
    strBuild.Append("</title></head><body><table><tr><td colspan='3'><h1>");
    
    strBuild.Append(objRequest.Path);
    
    strBuild.Append("</h1></td></tr>");
    
    if (objDirInfo.Exists)
    
    {
    
    FileInfo[] objArFile = objDirInfo.GetFiles();
    
    for (int miind = 0; miind < objArFile.Length; miind++ )
    
    {
    
    if (miind % 3 == 0 && miind<objArFile.Length-1)
    
    {
    
    strBuild.Append("<tr>");
    
    }
    
    FileInfo objFile=objArFile[miind];
    
    if (objFile.Extension == ".png" || objFile.Extension == ".jpg" || objFile.Extension == ".gif")
    
    {
    
    Image objImag=Image.FromFile(objFile.FullName);
    
    strBuild.Append("<td><a href='");
    
    strBuild.Append(objFile.Name);
    
    strBuild.Append("'><img border=0 src='");
    
    strBuild.Append(objFile.Name);
    
    strBuild.Append("' width=100></a><br>");
    
    strBuild.Append("Width : " + objImag.Width + " Height :" + objImag.Height);
    
    strBuild.Append("</td>");
    
    }
    
    if (miind % 3 == 0 && miind < objArFile.Length - 1 && miind>0)
    
    {
    
    strBuild.Append("</tr>");
    
    }
    
    }
    
    strBuild.Append("</table></body></html>");
    
    objResponse.Write(strBuild.ToString());
    
    }
    
    }
    
    

    On ajoute l’appel a notre module dans le fichier de configuration :

    <?xml version="1.0" encoding="UTF-8"?>
    
    <configuration>
    
    <system.webServer>
    
    <modules>
    
    <remove name="DirectoryListingModule" />
    
    <add name="DirectoryListingModule" type="MyIIS7Modules.MyModule" />
    
    </modules>
    
    </system.webServer>
    
    </configuration>
    
    

    Dans ce fichier je commence par retirer le module de DirectoryBrowsing pour rajouter le mien.

    Et voila le résultat :

    Et si on incluait WPF/E SilverLight

    SilverLight est le nom officiel de la technologie WPF/E. Cette nouvelle technologie permet une plus grande interactivité sur le web.
    Je vais reprendre le page turn du Sample Pack de silver light.
    Les modifications apportées au Page turn sont :

    • Tout le code sur une page, j’ai fusionné les javascript, le xaml et le HTML dans la même page
    • Les images peuvent avoir n’importe quel nom et plus un nom commençant par Page

    Pour des raisons de facilité j’ai tout mis dans un fichier texte compilé avec le module.
    Voici donc mon nouveau directory browsing :

    Le code se trouve dans la partie download du site.

    Conclusion :

    La modularité nouvelle d’IIS 7 permet de faciliter grandement le développement sur cette plateforme. En plus comme tout est dans le fichier de configuration du site la simplicité de déploiement, d’activation et de désactivation des modules rend le développement très souple. Le serveur web est entièrement personnalisable et customisable. Je conseille vivement la lecture des différents articles sur le site IIS.NET.

  • DWM Api et MILCore

    Introduction

    J'avais déjà écris des articles sur la programmation de l'api DWM mais ce n'était pas complet, car l'api DWM permet de faire beaucoup de choses. Commençons par un petit résumé.
    La DLL DWAPI.DLL permet de dialoguer directement avec le Destop Window Manager de vista, il est ainsi possible de travailler avec les effets de vista. Pour cela on doit d'abord verifier qu'on est bien en mode composition. C'est cette fonction qui permet de faire le test:

    [DllImport("dwmapi.dll", PreserveSig = false)] 
    static extern bool DwmIsCompositionEnabled(); 

    Elle retourne un boolean en fonction.

    Blur Effect

    On a la possibilité de mettre l'effet Blur dans la zone cliente. Pour cela je créé un projet WPF et dans ma fenétre je mets un bouton:

    Comme je vais manipuler cette fenêtre avec des fonctions de l'api Win32 il me faut sont Handle, c'est à dire son ID pour windows. En WPF on n'a pas de propriété Hwnd on doit la récupérer avec l'objet WindowInteropHelper de la maniére suivante:

    IntPtr hwnd = new WindowInteropHelper(window).Handle;

    Attention cette fonction ne retourne un Handle QUE DANS LE FORM LOAD pas dans le constructeur de la classe.

    Pouvoir mettre le Blur dans la zone cliente, je vais utiliser cette fonction:

    [DllImport("dwmapi.dll", PreserveSig = false)]
    static extern void DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS margins); 

    Cette fonction prend en paramétre une structure MARGINS que voici:

    [StructLayout(LayoutKind.Sequential)] 
    struct MARGINS 
    { 
    public MARGINS(Thickness t) 
    { 
    Left = (int)t.Left; 
    Right = (int)t.Right; 
    Top = (int)t.Top; 
    Bottom = (int)t.Bottom; 
    } 
    
    public int Left; 
    public int Right; 
    public int Top; 
    public int Bottom; 
    
    } 
    
    

    Cette structure definit les marges de la zone qui sera considérée comme cliente.Voici donc le code XAML:

    <Window x:Class="WPFBlur.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPFBlur" Height="300" Width="300" Loaded="Form_Loaded"
    >
    <Grid>
    <Button Height="23" Margin="102,104,115,0" Name="button1" VerticalAlignment="Top" >Button</Button>
    </Grid>
    </Window> 

    Et Voici le code C#:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Shapes;
    using System.Runtime.InteropServices;
    using System.Windows.Interop; 
    
    namespace WPFBlur
    {
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary> 
    
    public partial class Window1 : System.Windows.Window
    {
    [DllImport("dwmapi.dll", PreserveSig = false)]
    static extern void DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS margins); 
    
    [DllImport("dwmapi.dll", PreserveSig = false)]
    static extern bool DwmIsCompositionEnabled(); 
    
    public Window1()
    {
    InitializeComponent();
    button1.Background = new LinearGradientBrush(Color.FromArgb(60, 0, 255, 0), Color.FromArgb(60, 255, 0, 0), 60); 
    }
    private void Form_Loaded(object sender, RoutedEventArgs rea)
    {
    ExtendGlassFrame(this, new Thickness(-1)); 
    
    }
    public static bool ExtendGlassFrame(Window window, Thickness margin)
    { 
    
    if (!DwmIsCompositionEnabled())
    return false; 
    
    IntPtr hwnd = new WindowInteropHelper(window).Handle; 
    
    if (hwnd == IntPtr.Zero)
    throw new InvalidOperationException("La fenêtre doit être visible avant d’étendre la frame sur la zone cliente"); 
    
    window.Background = Brushes.Transparent; 
    
    HwndSource.FromHwnd(hwnd).CompositionTarget.BackgroundColor = Colors.Transparent; 
    
    MARGINS margins = new MARGINS(margin);
    DwmExtendFrameIntoClientArea(hwnd, ref margins);
    return true; 
    
    }
    [StructLayout(LayoutKind.Sequential)]
    struct MARGINS
    { 
    
    public MARGINS(Thickness t)
    {
    Left = (int)t.Left;
    Right = (int)t.Right;
    Top = (int)t.Top;
    Bottom = (int)t.Bottom;
    } 
    
    public int Left;
    public int Right;
    public int Top;
    public int Bottom;
    } 
    
    }
    } 
    
    

    Bien cela donne ceci:

    Evidemment on peut prendre l'effet Blur mais pas mettre la transparence. Dans ce cas voici ce qu'il faut changer. D'abord on utilisera cette fonction:

     

    [DllImport("dwmapi.dll", PreserveSig = false)]
    static extern void DwmEnableBlurBehindWindow(IntPtr hwnd, ref DWM_BLURBEHIND blurBehind); 

    Elle prend en paramétre le handle de la fenetre et une structure qui lui indique comment vas se mettre la transparence et le Blur de l'application:

    [StructLayout(LayoutKind.Sequential)]
    struct DWM_BLURBEHIND
    {
    public uint dwFlags;
    public bool fEnable;
    public IntPtr hRgnBlur;
    public bool fTransitionOnMaximized;
    } 

    La prémiére propriété est un flag qui indique comment mettre en place le blur voici les constantes qui entrent en ligne de compte pour ce flags:

    public const uint DWM_BB_ENABLE = 0x00000001; 
    public const uint DWM_BB_BLURREGION = 0x00000002; 
    public const uint DWM_BB_TRANSITIONONMAXIMIZED = 0x00000004; 

    Voici le code de la méthode qui permettra de supprimer la transparence de la fenêtre:

    public bool EnableBlurBehindWindow(Window wnd)
    { 
    if (!DwmIsCompositionEnabled()) 
    return false; 
    IntPtr hwnd = new WindowInteropHelper(wnd).Handle; 
    if (hwnd == IntPtr.Zero) 
    throw new InvalidOperationException("The Window must be shown before extending glass."); 
    DWM_BLURBEHIND objBlur = new DWM_BLURBEHIND(); 
    objBlur.fEnable = true; 
    objBlur.dwFlags = DWM_BB_ENABLE | DWM_BB_TRANSITIONONMAXIMIZED; 
    objBlur.fTransitionOnMaximized = true; 
    DwmEnableBlurBehindWindow(hwnd, ref objBlur); 
    return true; 
    } 

    Voici le résultat:

     

    Allons plus loin.

    Avec l'api DWM il y a la possibilité de contrôler la couleur des fenêtres et le théme qui est appliqué a windows. Prenons un exemple simple, la charge de la batterie. Ca me gave souvent d'aller voir a coté de l'heure ou en est la batterie. Ce qui pourrait être sympa c'est d'avoir la couleur des fenêtres qui changent de Vert vers Rouge par rapport au pourcentage de la battery. Pour pouvoir mettre à jours la couleur du thémes on utilisera la fonction suivante:

     

    [DllImport("dwmapi.dll", EntryPoint = "#104")]
    private static extern int DwmpSetColorization(uint ColorizationColor, bool ColorizationOpaqueBlend, uint Opacity); 

    Bien entendu on doit en premier temps sauvegarder ce que l'utilisateur avait choisi. Pour récupérer les informations de couleur du théme on utilisera cette fonction:

    [DllImport("dwmapi.dll")]
    private static extern void DwmGetColorizationColor(out uint ColorizationColor, out bool ColorizationOpaqueBlend); 

    Premiérement j'ai créé une application WPF avec visual studio. Dans cette application j'ai ajouté un timer qui regarde la charge de la batterie toutes les 10 secondes. Suivant l'état de la batterie il créé une couleur qui passe du vert au rouge. L'application mets cette couleur comme couleur de fenêtre.
    Voici le code complet de l'application:

     

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Shapes;
    using System.Timers;
    using System.Windows.Forms;
    using System.Runtime.InteropServices; 
    
    namespace ThemeBatteryCharge
    {
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary> 
    
    public partial class Window1 : System.Windows.Window
    {
    private System.Timers.Timer timer=null;
    uint myColor = 0;
    bool myOpacity = false;
    public Window1()
    {
    DwmGetColorizationColor(out myColor, out myOpacity);
    timer = new System.Timers.Timer();
    InitializeComponent();
    timer.Interval = 10000;
    timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
    timer.Start();
    timer_Elapsed(null, null);
    }
    [DllImport("dwmapi.dll", EntryPoint = "#104")]
    private static extern int DwmpSetColorization(uint ColorizationColor, bool ColorizationOpaqueBlend, uint Opacity); 
    
    [DllImport("dwmapi.dll")]
    private static extern void DwmGetColorizationColor(out uint ColorizationColor, out bool ColorizationOpaqueBlend); 
    
    void timer_Elapsed(object sender, ElapsedEventArgs e)
    { 
    
    float battCharge = SystemInformation.PowerStatus.BatteryLifePercent;
    System.Drawing.Color themeColor = System.Drawing.Color.FromArgb(200, (byte)(255 / (battCharge + 1)), (byte)(255 * battCharge), 0); 
    
    if (SystemInformation.PowerStatus.PowerLineStatus == System.Windows.Forms.PowerLineStatus.Offline)
    { 
    
    DwmpSetColorization((uint)themeColor.ToArgb(), false, 0);
    }
    else
    { 
    DwmpSetColorization(myColor, myOpacity, 0);
    }
    } 
    
    protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
    {
    base.OnClosing(e);
    DwmpSetColorization(myColor, myOpacity, 0);
    } 
    
    }
    } 
    
    

    Voici ce que cela donne par rapport aux différentes charge de la battery, toutes les fenêtres de Windows sont mise à jours:

  • Publier ses entités métiers avec la technologie WCF

    Introduction

    La technologie Windows Communication Foundation (WCF) propose deux méthodes en vue de définir au sein de contrats de services les structures de données qui seront amenés à être échangées entre un service et un consommateur. Ces deux méthodes sont les suivantes : DataContract ou MessageContract. Comme leur nom l'indique, chacune suit une orientation spécifique respectivement donnée ou message mais elles ne sont en aucun cas antinomiques.

    La première méthode est la plus instinctive pour qui connait depuis sa plus tendre enfance la programmation orientée objets, elle est dans la lignée de tout ce qui a été fait précédemment avec des technologies telles que Remoting ou WebService. La technologie WebService permettait bien une intervention sur les messages échangés, mais celle-ci était le plus souvent utiliser pour insérer des entêtes spécifiques aux messages et ne modifiait pas la moindre structure de données propres aux entités métiers échangés.
    La méthode DataContract s'inscrit donc logiquement dans la voie historique de la modélisation des données lors d'une communication.
     La méthode MessageContract est quant à elle une nouvelle solution. Mais elle n'est en aucun cas une rupture avec le passé, il ne s'agit pas de s'écarter de la programmation objet. Cette méthode permet avant tout de travailler au niveau du message véhiculé. Elle offre des outils de programmation facilitant grandement ce travail par rapport à ce qui avait été fait par le passé.

    La suite de ce document vous présentera les deux méthodes en citant pour chacune ces faiblesses et ces avantages.

    Vous avez dit contrat données ? Vous vouliez dire DataContract ?

    Comme nous le disions en introduction, il est peu probable que cela ne soit pas cette méthode que vous utiliserez en premier pour publier les entités manipulés par votre premier service WCF puisqu'il n'y aura probablement aucune raison qui vous obligent à intervenir sur les messages qui encapsuleront les entités.

    Chacune de vos classes métier doit être décorée par l'attribut DataContract, et chaque membre de celle-ci que l'on veut publier le sera par l'attribut DataMember.

    Cette publication passe par une sérialisation des données décorées. Cette sérialisation utilise par défaut le sérialisateur DataContractSerializer qui a pour lui d'être compatible avec le sérialisateur Xml bien connu de la technologie WebService : XmlSerializer.

    [DataContract(Namespace = "http://example.org/person")]
    public class Person
    {
        [DataMember(Name = "first", Order = 0)]
        public string First;
    
        [DataMember(Name = "last", Order = 1)]
        public string Last;
    
        [DataMember(IsRequired = false, Order = 2)]
        private string id;
    }

    Les types supportés par le sérialisateur DataContractSerializer sont les suivants :

    • CLR built-in types
    • Byte array, DateTime, TimeSpan, GUID, Uri, XmlQualifiedName, XmlElement et XmlNode array
    • Les enumerations (Enum)
    • Les classes décorées par l'attribut DataContract ou l'attribut CollectionDataContract.
    • Les classes qui implémentent l'interface IXmlSerializable
    • Les tableaux (Array) et les collections (y compris List<T>, Dictionary<K,V> et Hashtable)
    • Les classes décorées par l'attribut Serializable ou implémentant l'interface ISerializable.

    Le sérialisateur DataContractSerializer est capable de sérialiser un champ privé. Nous pouvons y voir une conséquence de sa compatibilité avec le sérialisateur XmlSerializer.

    Il est à noter que la technologie WCF supporte l'encodage Remote Procedure Call (RPC). Les mots clefs ref et out peuvent donc très bien être utilisés en préfixe des paramètres des méthodes d'un service WCF.

    Une lecture attentive de l'exemple de code présenté précédemment fait noter les différents paramètres liés à l'utilisation de l'attribut DataContract ou de l'attribut DataMember :

    • Namespace permet de spécifier le namespace lié à l'entité et qui sera utilisé lors de la création du message SOAP.
    • Order permet de spécifier l'ordre de sérialisation des membres de votre entité. Cela peut se révéler fort utile lorsqu'une de vos propriétés fait appel à une autre et donc que sa sérialisation dépend de celle précédemment faite de la propriété qu'elle référence.
    • IsRequired permet de spécifier que le membre dit de votre entité est obligatoire en vue de réaliser la sérialisation.

    Ces différents paramètres permettent bien entendu d'intervenir sur le message SOAP qui sera généré lors d'une communication entre un service WCF et son client. Mais cette intervention se limite à vos données et n'agit pas sur la structure même du message.

    Références Cycliques

    La technologie WCF ne supporte pas par défaut les références cycliques. La technologie WebService (Asmx) permettait de telles références via une modification du choix d'encodage des communications. Il fallait modifier celui sélectionné par défaut l'encodage Document Literal pour appliquer un encodage de type RPC. Le prix à payer était une perte au niveau des performances.

    Bien entendu, il est recommandé de ne pas se trouver face à une telle problématique mais dans une telle situation, il peut être envisagé de spécialiser le comportement du sérialisateur DataContractSerializer :

    1. Via une implémentation de la classe DataContractSerializerOperationBehavior et le référencement de celle-ci au niveau des comportements personnalisés de votre service WCF.

    [DataContract(Namespace = "http://example.org/person")]
    [KnownType(typeof(AnimalCollection))]   
    public class Person
    {
      [DataMember(Name = "AnimalName", Order = 0)]
      public AnimalCollection AnimalCollection
      {
        get { return _animalCollection; }
        set { _animalCollection = value; }
      }
    }

    1. Via le développement d'un attribut personnalisé héritant de l'interface IOperationBehavior et son utilisation comme décoration des opérations sérialisant des données ayant des références cycliques.

    Pour de plus amples détails quant à cette problématique, le lecteur est invité à lire le post «  Preserving Object Reference in WCF » présentant en détail les étapes d'implémentation à suivre.

    Devenir de la compatibilité avec la technologie WebService (Asmx)

    Le recours aux solutions énoncées si dessus ne vous garantit pas malheureusement une compatibilité avec la technologie Asmx. Si l'utilisation de la fonction AddWebReference à partir de l'environnement de développement Visual Studio ne génèrera aucune erreur, les références cycliques pointeront vers des objets nuls.

    Déclarer des types en vue de la sérialisation ou désérialisation d'une entité les référençant

    A l'instar du mécanisme XmlInclude offert par le sérialisateur XmlSerializer, la technologie WCF offre également un moyen de qualifier certains types de connus en vue de permettre la sérialisation d'un type les référençant : l'utilisation de l'attribut KnowType ([KnownType(typeToKnow)]).

    Si la propriété de l'entité à sérialiser a pour type une interface, il est nécessaire de déclarer un type comme connu implémentant cette interface.

    [DataContract(Namespace = "http://example.org/person")]
    [KnownType(typeof(AnimalCollection))]   
    public class Person
    {
      [DataMember(Name = "AnimalName", Order = 0)]
      public AnimalCollection AnimalCollection
      {
        get { return _animalCollection; }
        set { _animalCollection = value; }
      }
    }

    Conclusion

    Lors de ce chapitre, il n'a jamais été évoqué la moindre référence à la structure globale des messages qui seront échanges lors d'une communication avec un service WCF. Il est alors facile de comprendre pourquoi cette méthode est et restera la plus populaire, elle fait fi de la tuyauterie de communication utilisée par la technologie WCF et ainsi permet une adoption par le plus grand nombre. Malheureusement, l'hétérogénéité de votre environnement peut vous contraindre à intervenir sur la structure du message et donc vous amenez à utiliser les contrats de message.

    Vous avez dit contrat de messages ? Vous vouliez dire MesssageContract ?

    Comme son nom le laisse supposer, il ne s'agit plus d'intervenir sur vos entités mais également sur les messages dans laquelle elles viendront prendre place en vue d'établir une communication entre vos services WCF et leurs consommateurs.

    Bien entendu, l'emploi de l'attribut MessageContract se combine avec l'attribut DataContract présenté au message précédent. Ces méthodes ne sont pas antinomiques. Chacune ayant un champ d'intervention spécifique.

    Ainsi votre contrat de message contiendra le contrat de vos données métiers (DataContract) mais également celui lié à votre message en lui-même (Comment vos données seront-elles formatées dans votre message…).

    La technologie Asmx

    L'introduction se permettait une comparaison avec la technologie Asmx lorsque nous évoquions les différentes méthodes permettant d'exposer les contrats de vos entités métiers avec la technologie WCF.

    Si peu sont intervenus sur la structure globale du message construit par la technologie Asmx, celle-ci ne l'interdisait pas. Ces paramètres sont en réalité fort utile au sein d'environnement hétérogène, lorsque votre service et votre client ne repose pas sur la même plateforme technologique. A titre d'exemple, il vous était possible via l'utilisation de l'énumération SoapParameterStyle de spécifier la façon dont les paramètres étaient mis en forme dans le message.

    Il en va de même avec la technologie WCF, L'attribut MessageContract prend en paramètre IsWrapped qui vous permet d'agir sur la mise en forme des paramètres contenus dans votre message.

    [MessageContractAttribute(IsWrapped=true)]

    L'acronyme SOAP est très peu représenté en amont de ce chapitre, non pas que cette technologie ne soit pas utilisée lors de l'utilisation exclusive des contrats de données, mais il n'y avait aucune raison de l'évoquer. Pour ce qui est des contrats de messages, il est bon de se rappeler qu'un message SOAP est composé de deux parties : tête et corps. Il en ira donc de même pour vos contrats de messages publié à l'aide de la technologie WCF. Chacune partie se voit dédié un attribut : MessageHeader et MessageBodyMember.

    Il devient dés lors beaucoup plus aisé de manipuler des contrats de message, et la est une évolution notable apportée par la technologie WCF.

    [MessageContractAttribute]
    public class EchoPersonMessageRequest
    {
        [MessageHeader(Namespace="http://example.org/EchoPersonMessageRequest/Header", 
    
    ProtectionLevel=ProtectionLevel.EncryptAndSign) ]
        public Authorization authorization;
        [MessageBodyMember(Namespace="http://example.org/EchoPersonMessageRequest/Message", 
    
    ProtectionLevel=ProtectionLevel.EncryptAndSign)]
        public Person myPerson;
    }
    
    [DataContract(Namespace = "http://example.org/person")]
    public class Person
    {
      #region Fields
      private string _first;
      private string _last;
      [DataMember(IsRequired = false, Order = 2)]
      private string id;
      #endregion
    
      #region Property
      [DataMember(Name = "first", Order = 0)]
      public string First
      {
        get { return _first; }
        set { _first = value; }
      }
      [DataMember(Name = "last", Order = 1)]
      public string Last
      {
        get { return _last; }
        set { _last = value; }
      }
      #endregion
    }
    
    [ServiceContract]
    public interface IEchoService
    {
      [OperationContract]
      void EchoEmploye(EchoPersonMessageRequest msgRequest);
    }

    Un rapide coup d'œil vous fait noter les différents paramètres pris en compte par l'attribut MessageHeader :

    Le paramètre MustUnderstand, hérité de la technologie SOAP permet de spécifier si l'entête devra être obligatoirement analysé (comprise) par le destinataire ou l'entité renseigné par le paramètre Actor.

    Le plus intéressant des paramètres est ProtectionLevel. Il permet de spécifier si l'entête du message devra être signée, chiffrée ou les deux. C'est également un paramètre de l'attribut MessageBodyMember. Ainsi les contrats de message permettent à l'inverse des contrats de données de mettre en place des options de sécurité.

    Par contre, l'utilisation de l'attribut KnowType est réservée au contrat de données. Si vous devez y recourir, vous utiliserez dés lors un DataContract.

    Les tableaux

    Il existe deux façons pour déclarer des tableaux au sein d'un contrat de message avec la technologie WCF :

    Ne rien faire. Utiliser les attributs MessageHeader et MessageBodyMember sur des propriétés de type Array. Le résultat de la sérialisation sera la création d'un élément ayant plusieurs fils chacun rattaché à un élément du tableau.

    Utiliser l'attribut MessageHeaderArray qui hérite de l'attribut MessageHeader. Le résultat de la sérialisation est la création d'un élément pour chaque élément du tableau sérialisé.

    Attention, l'utilisation de l'attribut MessageHeaderArray ne fonctionne que pour l'entête du message et les tableaux, il ne fonctionne pas avec les collections.

    Recommandations

    L'utilisation de contrat de message vous permet de ne pas à avoir à spécifier les paramètres d'une opération d'un service un à un. Au lieu de faire face à cette signature void EchoPerson(string firstName, string LastName ), vous utiliserez celle-ci void EchoPerson(EchoPersonMessageRequest).

    Vous pourrez au mieux contrôler toute modification des paramètres pris en compte par votre opération.

    De plus lors de la génération de vos proxys par l'utilitaire svcutil, en vue de garantir si vous le souhaitez une compatibilité avec la technologie WebService, créera en interne des contrats de message si vous n'en avez pas fait le choix.

    public Customer GetCustomer(int ID, out string ResponseMessage)
    {
       GetCustomerRequest inValue = new GetCustomerRequest();
       inValue.ID = ID;
       GetCustomerResponse retVal = ((ICMSService)(this)).GetCustomer(inValue);
       ResponseMessage = retVal.ResponseMessage;
       return retVal.Customer;
    }

    Ce qu'il ne fera pas si bien entendu vous avez choisi de créer des contrats de message.

    GetCustomerResponse ICMSService.GetCustomer(GetCustomerRequest request)
    {
      return base.InnerProxy.GetCustomer(request);
    }

    Performance

    L'entête du message et chaque corps de message sont sérialisés indépendamment les uns des autres. Le même espace de nommage est donc répété pour l'entête et chaque partie du corps du message le référençant.

    [MessageContract] 
    public class BankingTransaction 
    { 
      [MessageHeader] 
      public Operation operation; 
      [MessageBodyMember]
      public Account sourceAccount; 
      [MessageBodyMember]
      public Account targetAccount;
      [MessageBodyMember]
      public int amount; 
    }
    

    Pour améliorer les performances, surtout pour ce qui est de la taille des messages qui seront échangés, il est bon de fusionner l'ensemble des éléments dans une unique partie du corps du message. Pour se faire, il convient de créer une classe regroupant l'ensemble des parties du corps de message et de la définir comme un contrat de données. Les contrats de message utilisent donc les contrats de données.

    [MessageContract]
    public class BankingTransaction 
    { 
      [MessageHeader]
      public Operation operation;
      [MessageBodyMember]
      public OperationDetails details; 
    }
    [DataContract]
    public class OperationDetails
    { 
      [DataMember]
      public Account sourceAccount; 
      [DataMember]
      public Account targetAccount;
      [DataMember]
      public int amount; 
    }

    Conclusion

    Que cela soit les contrats de données ou bien les contrats de message, la technologie WCF offre des outils de programmation simplifiés. Les contrats de données vous permettent en quelques attributs de publier rapidement vos données métiers, quant aux contrats de messages ils permettent sans un investissement particulier de manipuler aisément les messages échangés avec vos applications avec un accès aux options de sécurité grandement facilité.

    Vous pouvez télécharger à cette adresse cet article au format PDF.

    Vous trouverz à cette autre adresse le code source associé à cet article.

  • WF dans Sharepoint 2007

    SharePoint 2007 est le nouveau produit server phare de Microsoft. Quand on m'a parlé de SharePoint la première fois, c'était sous SharePoint 2003 et on ne peut pas dire que j'étais emballé. Mais pour des raisons business, et commerciales, je fus obligé de m'y intéresser. Petit à petit je me mis à découvrir le logiciel et je fus assez bluffé par l'ensemble des fonctionnalités du produit, mais certain manquement était pour moi inexcusable, manque d'une corbeille, de workflow digne de ce nom, impossibilité de personnaliser l'interface utilisateur. Bref le produit était bon pour moi mais pas mature. Avec SharePoint 2007 on en est loin, le produit est réellement mature et l'ensemble des fonctionnalités est énorme.
    Le but de cet article est de vous montrer à travers un exemple simple, la validation d'une demande de congés, comment mettre en place un workflow de validation.

    Le processus

    Le processus de cet article sera une validation de note de frais toute simple. Quelqu'un entre une note de frais dans le système, une tache pour la comptabilité est créée, la personne va sur le site pour voire ses tâches, elle valide la note de frais.

    Les formulaires :

    Voici le formulaire de note de frais, Il est entièrement interactif avec le serveur sharepoint. Les ComboBox qui contiennent les informations du client et des projets sont dynamiques par rapport au contenu de listes qui se trouve dans sharepoint. Dans Sharepoint j'ai créé 3 listes :

    La première est une liste de type contact qui contient les clients de ma société, son nom est « client ».
    La deuxième est une custom liste « projet », qui à une colonne (look up) permettant de récupérer le nom complet du client. La dernière liste est une custom liste catégorie, qui contiendra les différentes catégories de frais possible.

    Ensuite je lance mon application office préférée (mais non pas word) c'est infopath.

    La je fait le design de mon formulaire

    Puis j'ajoute des data connections pour récupérer les informations de sharepoint.
    D'abord le client. La manipulation étant relativement simple (on va dans datasource sur la barre office puis manage data connection, on selectionne receive data, sharepoint list ou library, on met le nom du serveur et ensuite c'est assez explicite). Ensuite on selectionne la dropdown list client (clic avec le bouton droit et propriété) et dedans on clic sur « look up values from an external data source » on met le nom de sa source de données. Voila a quoi ca ressemble :

    Pour les projet on fait pareil mais on ajoute en plus « Filter Data »

    Cela permet de remplir la combobox au moment ou on selectionne le client. Avant qu'un client est selectionné on ajoute un conditionnal formating :

    Pour que la combo soit desactivée.

    Pour le total on ajoute la default value suivante :

    Pour le bouton submit on choisit

    Pour que le formulaire soit posté directement dans sharepoint enregistré puis fermé automatiquement.

    Voila notre formulaire est prêt.

    Recapitulons :

    Ce formulaire contient le titre de la demande, la liste des clients de la société (qui sont enregistrer dans la libraire contact de SharePoint), lorsqu'on choisit un client dans la liste la liste des projets du client est mise à jours par rapport au client sélectionné. Ensuite on sélectionne la catégorie de la dépense (catégorie enregistrée dans une librairie sharepoint). On indique la somme de la catégorie, comme c'est une repeat section on peut en mettre autant qu'on souhaite. Le total est calculé automatiquement. Puis on valide. La validation entraine le lancement du workflow.

    Voici le formulaire de démarrage du workflow.

    Ce formulaire permet d'indiquer qui sera la personne responsable de la validation de la note de frais, ici a part le bouton il n'y a rien de spécial. Pour le bouton on mets ceci

    La dataconnection pour le bouton est de ce style :

    Et voici le formulaire de tache :

    Ici on n'a pas grand-chose, juste une checkbox pour valider ou pas et un bouton qui posséde les même informations que le bouton du démarrage du workflow.

    Mon Workflow :

    Le processus c'est quoi ?

    En fait le workflow est instancié, on met le nom du comptable qui gère les notes de frais du service. Quand quelqu'un ajoute une note de frais le workflow commence, a ce moment la une tache est créer dans la liste des taches du portail. Ensuite on vérifie que la check box de validation est cochée. Lorsqu'elle est cochée on finit la tache. Pour cela, j'ai mis les activités suivantes.

    On developpe.

    Ici je vais expliquer ce que j'ai dans Visual Studio, j'expliquerais aussi comment le déployer.
    Je lance Visual Studio, et je créer un nouveau projet :

    Je sélectionne Sharepoint Server Sequential Workflow, ce type de projet est un projet WF mais customisé pour Sharepoint, le workflow principal hérite de « SharePointSequentialWorkflowActivity », et des activités supplémentaires spécifiques à sharepoint sont ajoutées. Le workflow est créé. J'ajoute à mon workflow, qui posséde déjà OnWorkflowActivated1, l'activité "Create Task", c'est cette activité qui va créer la tache associée à mon workflow, celle qui permettra de valider la note de frais. Ensuite j'ajoute une boucle While qui me permettra de boucler sur le changement de la tache, afin de vérifier que la checkbox de validation n'est pas cochée. Dans la boucle While j'ajoute OnTaskChanged qui permet de récupérer les informations sur le changement de la tache. Puis en dehors de la boucle While je mets un CompleteTask qui finit le workflow. Voici à quoi mon processus ressemble :

    Démarrage du workflow

    L'activité OnTaskActivate est le démarrage du workflow. Ici le démarrage du workflow est orchestré par un formulaire infopath qui demande les informations du validateur de la note de frais. Pour lire les informations on enregistre le formulaire de démarrage du workflow en tant que source (file/Save as source file). Un fichier XSD est créé en plus d'autres, qui nous permettra de générer une classe C# qui sera ajoutée au projet. Pour générer cette classe on utilise la ligne de commande XSD nomfichier.xsd /c puis on ajoute ce fichier au projet.

    On double clique sur OnWorkflowActivated et dans le gestionnaire d'événement ainsi créé on ajoute ce code :

    XmlSerializer serializer = new XmlSerializer(typeof(myFields)); XmlTextReader reader = new XmlTextReader(new System.IO.StringReader(this.workflowProperties.InitiationData)); myFields initwkfl = (myFields)serializer.Deserialize(reader); this.AssigneTo = initwkfl.txtComptable; 

    Ici on désérialize le contenu du XML posté par le formulaire d'initialisation et on le passe a la classe autogénérer a partir du schema XSD. Ensuite on lit le contenu de la textbox txtComptable et on l'assigne a une variable de classe

    La corrélation

    Dans tous les systèmes de workflow, le plus important c'est la corrélation, c'est-à-dire pouvoir se faire suivre les étapes en réactivant le bon workflow et pouvoir différentier chaque instance.

    Ici on va créer un token pour les taches. Dans les propriétés de createTask on va mettre un nom dans la propriété correlationToken en spécifiant que c'est le workflow1 qui contient ce token.

    On met le MEME nom sur OnTaskChanged et CompleteTask.

    Cette corrélation permettra au système de savoir de ou il vient et ou il doit aller quand la tâche est créé, changé ou finit.

    Accéder aux informations des taches.

    Pour créer une tache dans sharepoint il faut lui assigné un ID cela est fait avec la propriété TaskID de l'activité createtask, il faut aussi accéder aux propriétés de la tâche, comme le titre la personne qui doit executer la tâche, avec taskProperties. Voici la fenêtre des propriétés :

    Pour cela on clique sur le bouton avec les trois petits points et on selection bind to a new member. Pour ces deux champs on choisira field et on mettra un nom.

    Il nous faut aussi pouvoir lire les informations qui ont été modifiées quand on valide la tache. Pour cela on mets à jour la propriété AfterProperties :

    On peut maintenant coder dans la création de la tache. On va donc dans les events de create task et dans le seul event de cette activity on ajoute le code suivant :

    this.taskId=Guid.NewGuid();
    this.taskProps.Title = "Validation de la note de frais de " + this.workflowProperties.Originator;
    this.taskProps.Description = "Test";
    this.taskProps.AssignedTo = this.AssigneTo;
    

    Voila maintenant dans la clause while on spécifie qu'on utilise un codecondition comme condition, c'est-à-dire qu'on va codé le test.

    Cela créer la méthode suivante :

    private void OnValidation(object sender, ConditionalEventArgs e) {}

    Dans cette méthode tant que la propriété Result, de l'objet e instance de ConditionalEventArgs, est vrai on boucle. On peut donc créer une variable initialisé a false qui s'appelle isValidated et mettre le code suivant dans la fonction :

    e.Result = !isValidated;

    Bien maintenant il faut changer cette valeur pour qu'à un moment elle passe à true.

    Dans l'event OnTaskChanged on met le code suivant :

    isValidated = bool.Parse(this.afterProperties.ExtendedProperties["chkFinished"].ToString());

    Le deployment

    Pour déployer notre workflow, nous créons un répertoire netfxfactory dans le répertoire des features de sharepoint (c:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\FEATURES\). Dans ce répertoire nous mettons le repertoire forms avec les deux formulaires de taches et de démarrage de workflow, puis nous mettons les fichiers feature.xml et workflow.xml à la racine. C'est fichier les voici :

    Feature

    <?xml version="1.0" encoding="utf-8"?>
    <!-- _lcid="1033" _version="12.0.3111" _dal="1" -->
    <!-- _LocalBinding -->
    
    <!-- Insert Feature.xml Code Snippet here.  To do this:
    1) Right click on this page and select "Insert Snippet" (or press Ctrl+K, then X)
    2) Select Snippets->SharePoint Workflow->Feature.xml Code -->
    <Feature  Id="1FEBEEB6-86B9-44be-910E-862B9D4C567B"
              Title="NetfxFeature"
              Description="Fonctionnalité de validation de la note de frais"
              Version="12.0.0.0"
              Scope="Site"
              ReceiverAssembly="Microsoft.Office.Workflow.Feature, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"
              ReceiverClass="Microsoft.Office.Workflow.Feature.WorkflowFeatureReceiver"
              xmlns="http://schemas.microsoft.com/sharepoint/">
        <ElementManifests>
            <ElementManifest Location="workflow.xml" />
        </ElementManifests>
        <Properties>
            <Property Key="GloballyAvailable" Value="true" />
    
            <!-- Value for RegisterForms key indicates the path to the forms relative to feature file location -->
            <!-- if you don't have forms, use *.xsn -->
            <Property Key="RegisterForms" Value="forms\*.xsn" />
        </Properties>
    </Feature>

    Workflow :

    <?xml version="1.0" encoding="utf-8" ?>
    <!-- _lcid="1033" _version="12.0.3015" _dal="1"   -->
    <!-- _LocalBinding   -->
    
    <!-- Insert Workflow.xml Code Snippet here.  To do this:
    1) Right click on this page and select "Insert Snippet" (or press Ctrl+K, then X)
    2) Select Snippets->SharePoint Workflow->Workflow.xml Code -->
    
    <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
        <Workflow
             Name="NetfxWkfl"
             Description="Workflow de validation de la note de frais"
             Id="2F216DF4-7C01-4f84-9B93-48AB62FA1E06"
             CodeBesideClass="netfxWkfl.Workflow1"
             CodeBesideAssembly="netfxWkfl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=2356c27bb4cae2f8"
             TaskListContentTypeId="0x01080100C9C9515DE4E24001905074F980F93160"
             AssociationUrl="_layouts/CstWrkflIP.aspx"
             InstantiationUrl="_layouts/IniWrkflIP.aspx"
             ModificationUrl="_layouts/ModWrkflIP.aspx"
             StatusUrl="_layouts/WrkStat.aspx">
    
            <Categories/>
            <!-- Tags to specify InfoPath forms for the workflow; delete tags for forms that you do not have -->
            <MetaData>
                <Association_FormURN>urn:schemas-microsoft-com:office:infopath:startWkfl:-myXSD-2007-01-31T20-09-38</Association_FormURN>
                <Instantiation_FormURN>urn:schemas-microsoft-com:office:infopath:startWkfl:-myXSD-2007-01-31T20-09-38</Instantiation_FormURN>
                <Task0_FormURN>urn:schemas-microsoft-com:office:infopath:task:-myXSD-2007-02-01T15-19-59</Task0_FormURN>
            </MetaData>
        </Workflow>
    </Elements>

    Puis nous l'installons à l'aide de stsadm.

    Conclusion

    Cet article survol la mise en place d'un workflow dans sharepoint pour vous montrer dans un premier temps comment le mettre en place. Une version plus complète verra le jour prochainement. Ainsi que des articles sur WF

  • WPF

    Introduction

    Windows Presentation Foundation fait partie du dernier .net framework, à savoir le .net framework 3.0. Le framework 3.0 est composé du framework 2.0 auquel s'ajoute WCF (Windows Communication Foundation), WF (Windows Workflow Foundation), WPF (Windows Presentation Foundation) et infocard pour l'authentification des utilisateurs.
    Windows Presentation foundation permet de gérer les affichages des fenêtres. Celui-ci est basé sur directx.
    En effet jusque maintenant l'affichage des fenêtres sous Windows se faisait en utilisant GDI+, qui est un affichage basé sur les bitmap, un peu comme si votre écran était un énorme BMP que Windows s'amuse à redessiner quand il peut. Maintenant c'est différent, l'affichage se base sur directx et le vectoriel. En fait pour simplifier les choses Directx est la sous couche la plus base de l'api, suit MIL pour Microsoft Intégration Layer, puis WPF. L'avantage du vectoriel c'est que si on agrandit l'affichage, il n'y a pas de pixellisation comme le ferait un bitmap.

     

    Les outils pour développer

    Sous WPF on a un nouveau langage qui est apparut, le XAML. Ce langage est un sous ensemble du XML. Le but est de séparer la description graphique de la couche métier, un peu comme le fait l'ASP.Net. Le but est de pouvoir déporter la mise en place de l'UI chez le graphiste et la couche métier pour le développeur. Des produits comme la gamme expression sont prévus pour le graphiste, et bien entendu visual studio pour le développeur.
    En fait pour avoir une bonne machine de développement pour WPF je vous conseil les choses suivante :
    - Un bonne OS serait windows Vista mais XP SP2 ou 2K3 SP1 suffit largement
    -Visual studio 2005 ou visual express je conseil C# mais VB.Net est un bon langage aussi
    -Windows Vista SDK afin d'avoir les outils nécessaire pour le xaml
    -WPF extension pour Visual Studio

    Voila vous êtes prés à développer.

    Visual Studio 2005 'Orcas'


    Prenons un peu en main VS et wpf :

    Nous allons créer dans un premier temps un projet avec visual studio :

     

    Pour cela nous sélectionnons un nouveau projet, dans le type de projet on sélectionne Visual C# / netframework 3.0. Visual Studio créé un template de projet avec tout ce qui est necessaire pour faire une application WPF. Examinons ce qui a été créé :

     

    Premiérement, toutes les références aux DLL nécessaire pour le projet ont été faite, à savoir :
    -PresentationCore
    -PresentationFramework
    -ReachFramework
    -WindowsBase

    Les deux dll qui commencent par UI sont la pour permettre la compatibilité avec les anciennes applications Win 32.

    Ensuite on a un fichier APP.XAML et APP.XAML.CS, c'est deux fichiers sont présents pour la gestion de l'application, c'est ces deux fichiers qui centralisent les événements de l'application et les propriétés de l'application.
    Les deux fichiers qui suivent sont les fichiers qu'on utilisera le plus dans l'application car c'est les fichiers qui contrôlent l'affichage de la fenêtre principale. Elle sera la fenêtre principale car on l'aura choisi.

    Le XAML

    Examinons le fichier XAML :

     

    <Window 
    x:Class="netfxfactoryApp1.Window1" 
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml 
      Title="netfxfactoryApp1" Height="300" Width="300">
      <Grid> 
      </Grid> 
    </Window> 

     

    Ce fichier commence par l'élément Window qui caractérise la description d'une fenêtre, sachez qu'il existe plusieurs élément racine pour un fichier XAML, ensuite nous avons l'attribut « x:Class » qui spécifie la classe mère du fichier XAML, c'est la classe dans laquelle sera implémenté toutes les informations concernant la gestion des événements. Puis nous avons la spécification des schémas parent du XAML a savoir http://schemas.microsoft.com/winfx/2006/xaml/presentation et http://schemas.microsoft.com/winfx/2006/xaml puis nous avons le titre de la fenêtre a savoir ici netfxfactoryapp1, la hauteur et la largeur. En fait dans l'élément Window on a toute les propriétés de la fenêtre qui pourront être indiqués.

     

    Le Contenu et le contenant

    La gestion du contenu est quelque peu différente en WPF par rapport aux applications Win32 classiques, en effet avant on ajoutait des contrôles sur la fenêtre principale, cette fenêtre pouvait contenir des conteneurs qui possédaient d'autres contrôles. On utilisait les containers pour pouvoir regrouper les contrôles entre eux mais le but restait relativement simple. En WPF on utilise des conteneurs qui servent surtout à gérer le layout des contrôles.

    Prenons un exemple, je veux mettre en place un formulaire de demande de coordonnées pour une personne dans ce formulaire je veux mettre en place un beau layout, je ne vais utiliser que des textbox et des textblock et une succession de conteneur, voici ce qui est à ma disposition.

    Voici dans un premier temps le code XAML :

     

    <Window x:Class="netfxfactoryApp1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Title="netfxfactoryApp1" Height="300" Width="300">
      <TextBlock>Nom</TextBlock>
    </Window> 

    Voici le résultat:


    Bien maintenant je veux ajouter une texbox a coté de mon textblock pour pouvoir entrer le nom, tout naturelement je pourrais être tenté de mettre ceci dans mon code :

     

    <Window x:Class="netfxfactoryApp1.Window1"
     xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
     xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
     Title="netfxfactoryApp1" Height="300" Width="300">
     <TextBlock>Nom</TextBlock>
     <TextBox/>
    </Window> 

     

    Le probléme c'est que maintenant le code ne compile plus et que j'ai une erreur de ce type :

     

    En fait Window ne peut avoir qu'un enfant, et si on regarde mon code, ici, il en a deux, un textblock et une textbox. Il faut donc trouver un moyen pour mettre en place un layout chouette pour notre formulaire. Pour cela on va utiliser un container comme enfant de window et ce container pourra avoir plusieurs enfants

    Commençons par un conteneur du style StackPanel :

     

    <Window x:Class="netfxfactoryApp1.Window1"
     xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
     xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
     Title="netfxfactoryApp1" Height="300" Width="300">
     <StackPanel>
      <TextBlock>Nom</TextBlock>
      <TextBox/>
      <TextBlock>Prenom</TextBlock>
      <TextBox/>
      <TextBlock>Adresse</TextBlock>
      <TextBox/>
      <Button>OK</Button>
     </StackPanel>
    </Window> 

    Cela donne ceci :

     

    Comme vous pouvez le voir les contrôles sont mis en pile dans la fenêtre. Surtout qu'on peut changer l'orientation :

     

    <Window x:Class="netfxfactoryApp1.Window1"
     xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
     xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
     Title="netfxfactoryApp1" Height="300" Width="300">
     <StackPanel Orientation="Horizontal">
     ………
      </StackPanel>
    </Window> 

    Voici le résultat :

     

    Bien entendu c'est beau mais pas vraiment ce que l'on souhaite.

    Passons au DockPanel. Le dockpanel se base sur la mise en place des contrôles par rapport aux cotés de la fenêtre et au centre de celle-ci. Commencons par le dockpanel sans informations sur la facon dont les contrôles doivent être placés :

     

    <Window x:Class="netfxfactoryApp1.Window1"
     xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
     xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
     Title="netfxfactoryApp1" Height="300" Width="300">
     <DockPanel>
      <TextBlock>Nom</TextBlock>
      <TextBox/>
      <TextBlock>Prenom</TextBlock>
      <TextBox/>
      <TextBlock>Adresse</TextBlock>
      <TextBox/>
      <Button>OK</Button>
     </DockPanel>
    </Window> 

    Ce qui donne ceci :

     

    Peu de différence avec le stack panel horizontal hormis le bouton OK qui prend toute la place. En fait en dock panel toute la surface de la fenêtre sera prise. Commencons pas spécifier pour chaque contrôle de se « docker » en haut de la fenêtre sauf pour le bouton qui lui ira en bas :

     

    <Window x:Class="netfxfactoryApp1.Window1"
     xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
     xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
     Title="netfxfactoryApp1" Height="300" Width="300">
     <DockPanel>
      <TextBlock DockPanel.Dock="Top">Nom</TextBlock>
      <TextBox DockPanel.Dock="Top"/>
      <TextBlock DockPanel.Dock="Top">Prenom</TextBlock>
      <TextBox DockPanel.Dock="Top"/>
      <TextBlock DockPanel.Dock="Top">Adresse</TextBlock>
      <TextBox DockPanel.Dock="Top"/>
      <Button DockPanel.Dock="Bottom">OK</Button>
     </DockPanel>
    </Window> 

     

     

     

    Jolie on aurait pu faire toute sorte de layout en se basant uniquement sur les cotés de la fenêtre. Par exemple enlevons les Dock properties pour les textbox par défaut elles prendront la valeurs left :

    Voici donc le résultat :

     

    Continuons notre tours des différents conteneurs, passons maintenant au WrapPanel (alias FlowLayout), la particularité de ce panel est qu'il adapte son contenu à sa dimension en faisant des retours a la ligne automatique, un peu comme le ferais une page HTML. Voyons le code :

     

    <Window x:Class="netfxfactoryApp1.Window1"
     xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
     xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
     Title="netfxfactoryApp1" Height="300" Width="300" >
     <WrapPanel>
      <TextBlock>Nom</TextBlock>
      <TextBox Width="100"/>
      <TextBlock>Prenom</TextBlock>
      <TextBox Width="100"/>
      <TextBlock>Adresse</TextBlock>
      <TextBox Width="100"/>
      <Button>OK</Button>
      </WrapPanel>
    </Window> 

    Ce qui donne:

     

    Et si on agrandit:

     

    Comme vous pouvez le voir le contenu s'adapte automatiquement. Mais voila le layout me convient toujours pas. Je continu avec le tableau. Il réagit de la même maniére qu'un tableau HTML. Voyons d'abord ce que l'on veut. L'idéal serait un tableau de deux colonnes sur 3 lignes dans les cellules de gauche on mettrait les textblock et dans celle de droite les textbox, le bouton sera sur la derniere ligne et occupera les deux colonnes. Voici le code :

     

    <Window x:Class="netfxfactoryApp1.Window1"
     xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
     xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
     Title="netfxfactoryApp1" Height="300" Width="300"> 
     <Grid>
      <Grid.ColumnDefinitions>
       <ColumnDefinition/>
       <ColumnDefinition/>
      </Grid.ColumnDefinitions>
      <Grid.RowDefinitions>
       <RowDefinition/>
       <RowDefinition/>
       <RowDefinition/>
       <RowDefinition/>
      </Grid.RowDefinitions>
      <TextBlock Grid.Column="0" Grid.Row ="0">Nom</TextBlock>
      <TextBox Width="100" Grid.Column="1" Grid.Row ="0"/>
      <TextBlock Grid.Column="0" Grid.Row ="1">Prenom</TextBlock>
      <TextBox Width="100" Grid.Column="1" Grid.Row ="1"/>
      <TextBlock Grid.Column="0" Grid.Row ="2">Adresse</TextBlock>
      <TextBox Width="100" Grid.Column="1" Grid.Row ="2"/>
      <Button Grid.Column="0" Grid.Row ="3" Grid.ColumnSpan="2">OK</Button>
     </Grid>
    </Window> 

    Voici le résultat :

     

    Bien entendu en spécifiant des tailles et un alignement on peut avoir une plus belle fenêtre encore :

     

    <Window x:Class="netfxfactoryApp1.Window1"
     xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
     xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
     Title="netfxfactoryApp1" Height="300" Width="300">
     <Grid>
      <Grid.ColumnDefinitions>
       <ColumnDefinition/>
       <ColumnDefinition/>
      </Grid.ColumnDefinitions>
      <Grid.RowDefinitions>
       <RowDefinition/>
       <RowDefinition/>
       <RowDefinition/>
       <RowDefinition/>
      </Grid.RowDefinitions>
      <TextBlock Grid.Column="0" Grid.Row ="0">Nom</TextBlock>
      <TextBox Width="100" VerticalAlignment="Top" Height="20" Grid.Column="1" Grid.Row ="0"/>
      <TextBlock Grid.Column="0" Grid.Row ="1">Prenom</TextBlock>
      <TextBox Width="100" VerticalAlignment="Top" Height="20" Grid.Column="1" Grid.Row ="1"/>
      <TextBlock Grid.Column="0" Grid.Row ="2">Adresse</TextBlock>
      <TextBox Width="100" VerticalAlignment="Top" Height="20" Grid.Column="1" Grid.Row ="2"/>
      <Button HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="0" Grid.Row ="3" Grid.ColumnSpan="2">OK</Button>
     </Grid>
    </Window> 

     

    Le résultat est enfin ce qu'on voulait :

     

     

    Comme on a pu le voir dans certains cas les contrôles prennent le maximum de place. Par exemple reprenons le stackpanel :

     

    Cela vient de la valeur par defaut de la propriété « «HorizontalAlignment », assez curieux mais en fait par defaut, dans le stackpanel, cette propriété a la valeur strech, ceux qui veut dire qu'on prend toute la place. Si on remplace le code par le suivant :

     

    <Window x:Class="netfxfactoryApp1.Window1"
     xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
     xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
     Title="netfxfactoryApp1" Height="300" Width="300">
     <StackPanel>
      <TextBlock>Nom</TextBlock>
      <TextBox HorizontalAlignment="Left"/>
      <TextBlock>Prenom</TextBlock>
      <TextBox HorizontalAlignment="Right"/>
      <TextBlock>Adresse</TextBlock>
      <TextBox HorizontalAlignment="Center"/>
      <Button HorizontalAlignment="Stretch">OK</Button>
     </StackPanel>
    </Window> 

    J'obtiens ceci:

     

    Bien entendu je peux ajouter la propriété Width pour agrandir mes textbox. Pour le bouton le fait de spécifier Strech est inutile en pratique puisque c'est la valeur par defaut.
    En fait c'est la propriété Content de la class Window qui est mise à jours quand on lui ajoute des enfants. Si on regarde dans le SDK on se rend vite compte que beaucoup d'autre contrôle on cette propriété content. Comme le bouton par exemple. On pourrait donc imaginer quelque chose de farfelue a savoir, c'est le bouton OK qui contiendra le formulaire de demande de coordonées !

    Testons donc ceci :

     

    <Window x:Class="netfxfactoryApp1.Window1"
     xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
     xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
     Title="netfxfactoryApp1" Height="300" Width="300">
     <Button HorizontalAlignment="Stretch">
      <Grid>
       <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
       </Grid.ColumnDefinitions>
       <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition/>
        <RowDefinition/>
        <RowDefinition/>
       </Grid.RowDefinitions>
       <TextBlock Grid.Column="0" Grid.Row ="0">Nom</TextBlock>
       <TextBox Width="100" VerticalAlignment="Top" Height="20" Grid.Column="1" Grid.Row ="0"/>
       <TextBlock Grid.Column="0" Grid.Row ="1">Prenom</TextBlock>
       <TextBox Width="100" VerticalAlignment="Top" Height="20" Grid.Column="1" Grid.Row ="1"/>
       <TextBlock Grid.Column="0" Grid.Row ="2">Adresse</TextBlock>
       <TextBox Width="100" VerticalAlignment="Top" Height="20" Grid.Column="1" Grid.Row="2"/>
       <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="0" Grid.Row ="3" Grid.ColumnSpan="2">OK</TextBlock>
      </Grid>
     </Button>
    </Window> 

     

    Si on compile pas d'erreur ?? alors lançons l'application et voila ce qu'on a :

     

    Notre bouton contient bien le formulaire. En fait tout ce que vous mettez dans une fenêtre vous pouvez le mettre dans un bouton, a votre imagination de fonctionner maintenant.

    Transformation

    Tous les contrôles sont rendu en vectoriel et peuvent être transformé, c'est-à-dire tourner, agrandit rétrécit ou déplacer au runtime.

    Imaginons, je veux tourner mon bouton de 45° dans le sens horaire. Je vais donc dire a mon fichier XAML de Transformer le layout du bouton en appliquant une Rotation de 45° :

     

    <Window x:Class="netfxfactoryApp1.Window1"
     xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
     xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
     Title="netfxfactoryApp1" Height="300" Width="300">
     <Button HorizontalAlignment="Stretch">
      <Button.LayoutTransform>
       <RotateTransform Angle="45"/>
      </Button.LayoutTransform>
      <Grid>
       <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
       </Grid.ColumnDefinitions>
       <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition/>
        <RowDefinition/>
        <RowDefinition/>
       </Grid.RowDefinitions>
       <TextBlock Grid.Column="0" Grid.Row ="0">Nom</TextBlock>
       <TextBox Width="100" VerticalAlignment="Top" Height="20" Grid.Column="1" Grid.Row ="0"/>
       <TextBlock Grid.Column="0" Grid.Row ="1">Prenom</TextBlock>
       <TextBox Width="100" VerticalAlignment="Top" Height="20" Grid.Column="1" Grid.Row ="1"/>
       <TextBlock Grid.Column="0" Grid.Row ="2">Adresse</TextBlock>
       <TextBox Width="100" VerticalAlignment="Top" Height="20" Grid.Column="1" Grid.Row ="2"/>
       <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="0" Grid.Row ="3" Grid.ColumnSpan="2">OK</TextBlock>
      </Grid>
     </Button>
    </Window> 

     

     

    Assez bluffant non.

    DataBinding entre contrôles

    Une des fonctionnalités les plus « Sexy » de WPF est la possibilité de faire du databinding entre les différents contrôles de l'application. Prenons un exemple, j'ai envie de tourner mon bouton de quelque degrées comme je l'ai fait au dessus mais en m'aidant d'un slider. Ca serait mieux l'utilisateur pourrait controler la rotation au runtime.
    Ajoutons donc le slider au layout de ma fenêtre :

     

    <Window x:Class="netfxfactoryApp1.Window1"
     xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
     xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
     Title="netfxfactoryApp1" Height="300" Width="300">
     <StackPanel>
      <Slider Minimum="0" Maximum="360"/>
      <Button HorizontalAlignment="Stretch">
       <Button.LayoutTransform>
       <RotateTransform Angle="45"/>
      </Button.LayoutTransform>
      <Grid>
       <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
       </Grid.ColumnDefinitions>
       <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition/>
        <RowDefinition/>
        <RowDefinition/>
       </Grid.RowDefinitions>
       <TextBlock Grid.Column="0" Grid.Row ="0">Nom</TextBlock>
       <TextBox Width="100" VerticalAlignment="Top" Height="20" Grid.Column="1" Grid.Row ="0"/>
       <TextBlock Grid.Column="0" Grid.Row ="1">Prenom</TextBlock>
       <TextBox Width="100" VerticalAlignment="Top" Height="20" Grid.Column="1" Grid.Row ="1"/>
       <TextBlock Grid.Column="0" Grid.Row ="2">Adresse</TextBlock>
       <TextBox Width="100" VerticalAlignment="Top" Height="20" Grid.Column="1" Grid.Row ="2"/>
       <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="0" Grid.Row ="3" Grid.ColumnSpan="2">OK</TextBlock>
      </Grid>
     </Button>
    </StackPanel>
    </Window> 

     

    Comme vous le savez déjà la fenetre ne peut avoir qu'un enfant j'ai donc mis un stackpanel en plus . Et voici le résultat :

     

    Mais pour l'instant rien ne se passe. En fait je dois nommer le slider pour pouvoir lier l'angle de rotation de mon bouton a mon slider, pour eviter un effet de redimensionnement génant j'ai aussi Fixer la taille de mon bouton.

     

    <Window x:Class="netfxfactoryApp1.Window1"
     xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
     xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
     Title="netfxfactoryApp1" Height="300" Width="300">
     <StackPanel>
      <Slider Minimum="0" Maximum="360" Name="sldValue"/>
      <Button HorizontalAlignment="Stretch" Width="100">
       <Button.LayoutTransform>
        <RotateTransform Angle="{Binding ElementName=sldValue, Path=Value}"/>
       </Button.LayoutTransform>
       <Grid>
        <Grid.ColumnDefinitions>
         <ColumnDefinition/>
         <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
         <RowDefinition/>
         <RowDefinition/>
         <RowDefinition/>
         <RowDefinition/>
        </Grid.RowDefinitions>
        <TextBlock Grid.Column="0" Grid.Row ="0">Nom</TextBlock>
        <TextBox Width="100" VerticalAlignment="Top" Height="20" Grid.Column="1" Grid.Row ="0"/>
        <TextBlock Grid.Column="0" Grid.Row ="1">Prenom</TextBlock>
        <TextBox Width="100" VerticalAlignment="Top" Height="20" Grid.Column="1" Grid.Row ="1"/>
        <TextBlock Grid.Column="0" Grid.Row ="2">Adresse</TextBlock>
        <TextBox Width="100" VerticalAlignment="Top" Height="20" Grid.Column="1" Grid.Row ="2"/>
        <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="0" Grid.Row ="3" Grid.ColumnSpan="2">OK</TextBlock>
       </Grid>
      </Button>
     </StackPanel>
    </Window> 

    Le resultat est plutôt pas mal :

     

     

    StoryBoard

    Et si mon bouton s'animait tout seul ? Et oui les animations sont possibles en utilisant ce qu'on appel un storyboard. Comme dans l'animation télé.
    Pour cela je dois déjà définir quand mon storyboard va démarrer, pour cela j'utilise les Triggers de la classe window :

     

    <Window.Triggers>
     <EventTrigger RoutedEvent="Window.Loaded">
    <EventTrigger.Actions> 

    En effet ici j'utilise un RoutedEvent (on verra à quoi cela sert) pour spécifier que je veux travailler sur le chargement de ma fenêtre. Bien ensuite je défini un nom sur la RotateTransform de mon bouton :

     

    <RotateTransform x:Name="btnAngle" /> 

    Et J'applique mon storyboard qui pointe sur la RotateTransform. Ce qui définit mon storyboard c'est que la valeur de départ sera 0, la finale sera 360 (pour faire un tour complet), que le temps pour faire ce tour sera de 15 seconde, et qu'il faudra le répéter indéfiniment :

     

    <Storyboard>
     <DoubleAnimation From="0" To="360" 
        BeginTime="00:00:00" 
        Duration="00:00:15" 
        RepeatBehavior="Forever" 
        Storyboard.TargetName="btnAngle" 
        Storyboard.TargetProperty="Angle"/>
    </Storyboard> 

     

    Cette animation est basée sur les doubles donc c'est une DoubleAnimation.
    Voila le code au complet :

     

    <Window x:Class="netfxfactoryApp1.Window1"
     xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
     xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
     Title="netfxfactoryApp1" Height="300" Width="300">
     <Window.Triggers>
      <EventTrigger RoutedEvent="Window.Loaded">
       <EventTrigger.Actions>
        <BeginStoryboard>
         <Storyboard>
          <DoubleAnimation From="0" To="360" 
           BeginTime="00:00:00" 
           Duration="00:00:15" 
           RepeatBehavior="Forever" 
           Storyboard.TargetName="btnAngle" 
           Storyboard.TargetProperty="Angle"/>
          </Storyboard>
         </BeginStoryboard>
        </EventTrigger.Actions>
       </EventTrigger>
      </Window.Triggers>
      <Button Width="100" Height="100">
       <Button.LayoutTransform>
        <RotateTransform x:Name="btnAngle" />
       </Button.LayoutTransform>
       <Grid>
        <Grid.ColumnDefinitions>
         <ColumnDefinition/>
         <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
         <RowDefinition/>
         <RowDefinition/>
         <RowDefinition/>
         <RowDefinition/>
        </Grid.RowDefinitions>
        <TextBlock Grid.Column="0" Grid.Row ="0">Nom</TextBlock>
        <TextBox Width="100" VerticalAlignment="Top" Height="20" Grid.Column="1" Grid.Row ="0"/>
        <TextBlock Grid.Column="0" Grid.Row ="1">Prenom</TextBlock>
        <TextBox Width="100" VerticalAlignment="Top" Height="20" Grid.Column="1" Grid.Row ="1"/>
        <TextBlock Grid.Column="0" Grid.Row ="2">Adresse</TextBlock>
        <TextBox Width="100" VerticalAlignment="Top" Height="20" Grid.Column="1" Grid.Row ="2"/>
        <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="0" Grid.Row ="3" Grid.ColumnSpan="2">OK</TextBlock>
       </Grid>
      </Button>
    </Window> 

    Et voila le résultat :

     

    Evidement a moins de faire un gif animé, vous ne risquez pas de voire l'animation donc c'est a vous de la faire ;-) .

    Le code Behind

    Tout cela est bien mais pour l'instant notre application ne fait absolument rien. Il faudrait peut être commencé à développer quelque chose. Pour cela on va développer les actions en C#. Mais comment le xaml et le C# peuvent se connaitre ??
    Déjà dans le XAML on a une indication sur la classe qui est utilisée pour gérer les événements. En Effet l'attribut x :Class permet de donner le nom de la classe qui réceptionne les events. Ensuite la classe dans le programme C# est Partial, ce qui veut dire qu'elle n'est pas compléte et donc un bout est définit autre part. En fait quand on compile le fichier XAML est « transformé » en C# puis fusionner avec sa classe partielle qui est définit dans x :Class et enfin il est transformé en MSIL. Ici on veut définir que le clique sur le bouton générera un événement et affichera une message box avec les différentes informations contenu dans le formulaire. Pour cela on va changer le XAML pour déjà nommer tous les contrôles. On se basera sur le layout grid vus plus haut. Puis on spécifiera une valeur pour la propriéte click du bouton afin de rediriger le click sur l'appel à une fonction.

    Voici le code XAML :

     

    <Window x:Class="netfxfactoryApp1.Window1"
     xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
     xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
     Title="netfxfactoryApp1" Height="300" Width="300"> 
     <Grid>
      <Grid.ColumnDefinitions>
       <ColumnDefinition/>
       <ColumnDefinition/>
      </Grid.ColumnDefinitions>
      <Grid.RowDefinitions>
       <RowDefinition/>
       <RowDefinition/>
       <RowDefinition/>
       <RowDefinition/>
      </Grid.RowDefinitions>
      <TextBlock Grid.Column="0" Grid.Row ="0">Nom</TextBlock>
      <TextBox Width="100" VerticalAlignment="Top" Height="20" Grid.Column="1" Grid.Row ="0" Name="txtNom"/>
      <TextBlock Grid.Column="0" Grid.Row ="1">Prenom</TextBlock>
      <TextBox Width="100" VerticalAlignment="Top" Height="20" Grid.Column="1" Grid.Row ="1" Name="txtPrenom"/>
      <TextBlock Grid.Column="0" Grid.Row ="2">Adresse</TextBlock>
      <TextBox Width="100" VerticalAlignment="Top" Height="20" Grid.Column="1" Grid.Row ="2" Name="txtAdresse"/>
      <Button HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="0" Grid.Row ="3" Grid.ColumnSpan="2" Name="btnOK" Click="btnOK_Click">OK</Button>
     </Grid>
    </Window> 

     

    Et voici le code C# :

     

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Shapes;
    
    namespace netfxfactoryApp1 
    { 
      /// <summary>
      /// Interaction logic for Window1.xaml
      /// </summary>
      public partial class Window1 : System.Windows.Window
      {
       public Window1()
       {
        InitializeComponent();
       }
       protected void btnOK_Click(object sender, RoutedEventArgs rea)
       {
        string strMessage = string.Format("Bonjour {0}, {1} vous habitez bien {2}", txtNom.Text, txtPrenom.Text, txtAdresse.Text);
        MessageBox.Show(strMessage);
       }
      }
    } 

     

    Le résultat est le suivant:

     

    Bien il y a peu de différence au premier abord entre la gestion des events sous GDI+ et celle sous WPF. Ben en fait si. La notion de routedevent est très importante, en effet vous avez la possibilité de rediriger tous les RoutedEvent vers une seule gestion événementielle. Ici je vais demander d'afficher le nom du contrôle qui à déclencher l'événement. J'ajoute donc le code suivant a ma fonction C# qui gére l'événement :

    protected void btnOK_Click(object sender, RoutedEventArgs rea)
    {
     string strMessage = string.Format("Bonjour {0}, {1} vous habitez bien {2}\n C'est le contrôle avec le nom {3} qui a géré l'evenement mais c'est le contrôle {4} qui l'a vraiment déclenché", txtNom.Text, txtPrenom.Text, txtAdresse.Text, ((Control)sender).Name, ((Control)rea.OriginalSource).Name); 
    
     MessageBox.Show(strMessage);
    } 
    
    

    Bien le résultat actuel n'est pas très probant puisqu'on obtient ceci :

     

    En fait comme on n'utilise pas encore les routedevents, on n'a rien de spécial qui se passe. Mais si on souhaite que tous les click sur un bouton soit gérer de maniére centralisée, par exemple par la fenêtre. Cela peut être interressant quand on a des boutons a la fonctionnalité si proche qu'il est dommage d'avoir une répétition de la fonction partout. Par exemple une calculatrice, chaque clique sur un nombre a un business relativement identique. Voici le code que l'on mettrait dans le XAML :

     

    <Window x:Class="netfxfactoryApp1.Window1"
     xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
     xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
     Title="netfxfactoryApp1" Height="300" Width="300" Name="MainWnd" 
     Button.Click="btnOK_Click">
     <Grid>
      <Grid.ColumnDefinitions>
       <ColumnDefinition/>
       <ColumnDefinition/>
      </Grid.ColumnDefinitions>
      <Grid.RowDefinitions>
       <RowDefinition/>
       <RowDefinition/>
       <RowDefinition/>
       <RowDefinition/>
      </Grid.RowDefinitions>
      <TextBlock Grid.Column="0" Grid.Row ="0">Nom</TextBlock>
      <TextBox Width="100" VerticalAlignment="Top" Height="20" Grid.Column="1" Grid.Row ="0" Name="txtNom"/>
      <TextBlock Grid.Column="0" Grid.Row ="1">Prenom</TextBlock>
      <TextBox Width="100" VerticalAlignment="Top" Height="20" Grid.Column="1" Grid.Row ="1" Name="txtPrenom"/>
      <TextBlock Grid.Column="0" Grid.Row ="2">Adresse</TextBlock>
      <TextBox Width="100" VerticalAlignment="Top" Height="20" Grid.Column="1" Grid.Row ="2" Name="txtAdresse"/>
      <Button HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="0" Grid.Row ="3" Grid.ColumnSpan="2" Name="btnOK">OK</Button>
     </Grid>
    </Window> 

    J'ai ajouté un nom à la fenêtre pour qu'on puisse voir quelque chose. Voici le résultat :

     

    Comme vous pouvez voir le Sender c'est la fenêtre car c'est elle qui gère réellement l'événement mais grâce à la propriété OriginalSource on a le vrai nom du contrôle.

    Conclusion

    Voici donc la premiére partie de l'introduction au WPF terminée. Dans cette partie on a vue ce qu'était le XAML, comment fonctionner la gestion de contenu et de contenant avec les différents contrôles conteneurs. Puis on a vu comment fonctionne le code behind dans les applications Windows et les routed events. Il nous reste encore plein de chose a voir que j'aborderais ultérieurement.

    VANNESTE Xavier

  • Une nouvelle façon de communiquer avec ses services web

    Introduction

    Add Web Reference.

    La normalisation des différentes spécifications sur lesquelles s'appuie la technologie service web a permis aux éditeurs de produire des outils automatisant leur utilisation. Ainsi, la plateforme Microsoft.NET s'est dotée, dés sa première version, d'outils (« Add Web Reference », « Wsdl.exe ») permettant d'utiliser dans une application.NET des objets et des méthodes fournis par un service Web d'une façon comparable à ce que vous feriez pour vos propres objets.

    Mais la simplicité de ces outils ne peut malheureusement faire oublier leurs limites, révélées lors de leur emploi dans un environnement de production industriel et souvent contraignant.

    Qui n'a pas pesté contre la génération multiple, au sein de son environnement de développement, d'une même structure de données lors de la génération successive de plusieurs proxys liés à des services web pointant tous, pourtant, sur cette même structure de données ? Vous preniez pourtant bien soin, au sein de la boite de dialogue « Add Web Reference », d'indiquer un espace de nommage identique pour l'ensemble de vos proxys mais vous sous estimiez alors la perfidie de votre environnement de développement qui jouait à renommer vos espaces de nommage en les suffixant d'un nombre entier s'ils avaient déjà été utilisés dans un précédent ajout de référence web.

    Loin de vous satisfaire de cette situation, vous avez du soit vous résoudre à vous retrousser vos manches ou soit profiter des trésors dont la toile regorge parfois. Si vous adoriez enfant la chasse au trésor, vous avez surement cru faire fortune suite à la découverte de la librairie publié par Christian Weyer : DynWsLib. Si cette librairie offrait de nombreuses fonctionnalités dont une génération dynamique des proxys de vos services web, elle péchait également au niveau de la capitalisation des structures de données consommées par ces derniers.

    Tout vous conduisait à vous dispenser de l'outil « Add Web Reference » et de combiner vos efforts avec différents librairies prises à droite et à gauche sur la toile ou à jongler avec les paramètres offerts par l'outil Wsdl.exe.

    La publication de la version 2.0 de la plateforme Microsoft.NET ouvrit de nouvelles perspectives via :

    Mais vous veniez à peine de prendre note de ces nouvelles fonctionnalités, qu'une nouvelle interface de programmation Windows pour la communication était annoncée : Windows Communication Foundation (WCF).

    ChannelFactory

    Cette Api est une des fondations de la version 3.0 de la plateforme Microsoft.NET. Elle sonne le glas de plusieurs technologies de communication dont s'était dotée la plateforme .NET. Mais loin de dramatiser, il nous faut saluer cette initiative qui fédère désormais au sein d'un socle commun toutes les technologies de communication (Web Services, Remoting, MSMQ…) et au sein d'une seule classe toute l'intelligence que l'on peut injecter à un proxy : ChannelFactory<TChannel>.

    Ce dernier ainsi doté fait fi de toute génération de code préalable à son utilisation. Il permet de plus via le type paramétré TChannel de spécifier indirectement les structures de données que vous utiliserez lors de la communication avec votre service distant.

    Mais que faire en attendant le déploiement massif de cette nouvelle interface de programmation au sein des réseaux d'entreprise ?

    La suite de ce document vous présentera une solution permettant de tirer pleinement profit des fonctionnalités de la version 2.0 de la plateforme Microsoft.NET tout en vous préparant à votre future adoption de WCF et donc de réduire vos prochains couts de migration.

    Dynamic Proxy Generation

    Objectifs et problématiques.

    L'objectif est l'implémentation d'un mécanisme offrant une génération dynamique de proxy de service web dont le contrat serait spécifié par une interface. Ce mécanisme doit de plus être développé exclusivement à partir de la plateforme Microsoft.NET 2.0.

    Les problématiques sont donc les suivantes :

    • Génération dynamique d'un proxy.
    • Génération d'un proxy fortement typé.

    Ci-dessous, un exemple de code mettant en œuvre le mécanisme souhaité :

    ProxyFactory<IWebService> __proxyFactory = new ProxyFactory<IWebService>();
    IWebService __proxy = __proxyFactory.CreateProxy();
    string __helloWorld = __proxy.HelloWorld();

    Génération dynamique d'un proxy

    La génération dynamique de proxy avait été solutionnée bien avant la version 2.0 de la plateforme Microsoft.NET. C'est en nombre d'années que nous pouvons compter le moment où Christian Weyer nous faisait part de ses premiers travaux à ce sujet et du lancement du projet : DynWsLib.

    Ce dernier a su tirer le meilleur parti de la classe ServiceDescriptionImporter en vue de générer à la volée des proxys au sein d'une librairie sauvegardée dans un répertoire temporaire en vue de leur réutilisation future et donc de ne générer qu'une fois ces derniers.

    Limitations

    Malheureusement la classe ServiceDescriptionImporter a un comportement par défaut concernant la génération des structures de données identique à celui suivi par les outils présentés en introduction de ce document. Les structures de données qu'elle génère ont un scope d'utilisation réduit au proxy auquel elles sont associées.

    Solution

    Différentes solutions nous permettent de modifier le comportement par défaut de la classe ServiceDescriptionImporter concernant la génération des structures de données :

    • Agir sur le code généré suite à l'appel de la méthode Import de cette classe.
    • Utiliser la nouvelle classe SchemaImporterExtension mis à disposition par la plateforme Microsoft.NET dans sa version 2.0.

    Notre choix se porte bien entendu sur la seconde alternative, la première pouvant se révéler fort fastidieuse et non raisonnée.

    Génération d'un proxy fortement typé.

    Comme nous le soulignons dans l'introduction de ce document, il est fort peu aisé en utilisant les outils de génération de proxy de services web mis à disposition par la société Microsoft de spécifier les structures de données que nous souhaitons utiliser pour communiquer avec un service web.

    Une nouvelle fois, Microsoft a su réagir et propose désormais un mécanisme au sein de la version 2 .0 de sa plateforme de développement .NET répondant à nos attentes. Il s'agit de développer nos propres gestionnaires de correspondances entre des éléments XML schéma et des classes. Le développement de ces gestionnaires de correspondances personnalisés reposent sur l'implémentation de la classe abstraite SchemaImporterExtension. Nous ne focaliserons dans ce document uniquement sur une des méthodes de cette classe : ImportSchemaType. Le lecteur souhaitant plus de détails quant au fonctionnement de ces gestionnaires de correspondance est invité à lire l'article de Jelle Druyts intitulé « Customizing generated Web Service proxies in Visual Studio 2005 ».

    L'implémentation de la méthode ImportSchemaType(string name, string ns, …) nous permettra de lier nos propres structures de données aux différents éléments XML. Le paramètre name indique le nom de l'élément XML schéma dont nous devons établir si nous le souhaitons une correspondance avec un type de notre choix, quant au paramètre ns il nous informe de l'espace de nommage XML associé à cet élément XML. Nous indiquerons en retour de cette méthode le nom complet du type associé à l'élément XML schéma traité.

    Pour que vos gestionnaires de correspondance soient utilisés par la plateforme Microsoft.NET, il est nécessaire de les déclarer dans la section schemaImporterExtensions (configuration/system.xml.serialization/schemaImporterExtensions) du fichier de configuration de votre application ou de l'outil devant les utiliser.

    Limitations

    Cette solution repose sur la déclaration exclusive des gestionnaires de correspondance via l'utilisation d'un fichier de configuration.

    De plus, il n'est malheureusement pas possible de paramétrer nos gestionnaires de correspondances lors de leur initialisation puisque celle-ci fait appel à leurs constructeurs par défaut (sans paramètres).

    Solution

    Il ne nous est pas possible de reposer sur un fichier de configuration en vue de définir notre propre gestionnaire de correspondances. Ce dernier se devra de plus de faire correspondre aux éléments XML, définis dans le document wsdl du service web avec lequel nous désirons communiquer, des types que nous piocherons au sein des librairies référencées par l'interface TProxy.

    Paramétrage du gestionnaire de correspondance.

    Il nous est impossible de paramétrer notre gestionnaire de correspondance via l'ajout d'un nouveau constructeur. Nous allons alors tirer une première fois partie du support des génériques par la plateforme Microsoft .NET 2.0.

    Notre gestionnaire de correspondance sera à l'instar de notre générateur dynamique de proxy paramétré par l'interface TProxy : SchemaImporterExtension<TProxy>. Il pourra ainsi à partir de cette interface parcourir l'ensemble des types définis au sein des librairies référencées en vue de leur faire correspondre des éléments XML.

    Lors de cette correspondance, plusieurs paramètres sont à prendre en compte, en effet il est possible de personnaliser la sérialisation XML associée à une classe. Cette sérialisation XML personnalisée doit être supportée, l'unique élément à prendre en compte est la définition d'un espace de nommage XML au niveau du type (via l'attribut XmlRootAttribute) ou bien de l'interface TProxy (via l'attribut WebServiceAttribute).

    Nous avons décidé pour des raisons de performance de charger préalablement à l'établissement d'une première correspondance entre un élément XML schéma et un type, l'ensemble des types référencé par l'interface TProxy (TypeExporter) au sein d'un dictionnaire (XmlQualifiedNameTypeCollection) indexé par leur nom ainsi que l'espace de nommage XML s'il le redéfinisse.

    Figure 1 - SchemaImporterExtension<TProxy>Diagram class.

    Déclaration du gestionnaire de correspondances.

    Jusqu'à maintenant marchant dans les pas de la librairie DynWsLib, seule avait été évoquée la méthode Import de la classe ServiceDescriptionImporter pour générer un proxy. Mais cette classe s'est dotée depuis la version 2. 0 de la plateforme Microsoft.NET d'une méthode statique GenerateWebReferences répondant à la même problématique. Mais l'avantage de cette deuxième méthode est qu'elle permet de déclarer programmatiquement des gestionnaires de correspondances. La limite soulevée précédemment quant à l'utilisation de fichier de configuration n'est donc plus d'actualité.

    La déclaration d'un gestionnaire de correspondances s'appuie sur la classe WebReferenceOptions et la propriété SchemaImporterExtensions. Cette propriété est une collection de chaines de caractères, il vous suffit d'ajouter à cette collection le nom qualifié d'assembly du type de votre gestionnaire de correspondance pour qu'il soit utilisé lors de l'appel à la méthode GenerateWebReferences. Cette méthode prenant en paramètre une instance de la classe WebReferenceOptions.

    // Discover the wsdl document and its references. 
    DiscoveryClientProtocol __discoveryClientProtocol = new DiscoveryClientProtocol();
    __discoveryClientProtocol.DiscoverAny([Wsdl]);
    
    // Create a namespace and a unit for proxy client compilation.
    CodeNamespace __namespace = new CodeNamespace();
    CodeCompileUnit __cplUnit = new CodeCompileUnit();
    __cplUnit.Namespaces.Add(__namespace);
    
    // Create a web reference using the WSDL collection. 
    // The discovery documents are associated with the previously created code namespace.
    WebReference __webReference = new WebReference(__discoveryClientProtocol.Documents, __namespace);
    
    // Create a web reference collection which includes the previously instancied WebReference object.
    WebReferenceCollection __wbRefs = new WebReferenceCollection();
    __wbRefs.Add(__webReference);
    
    // Define options for the compilation of the proxy client. 
    CodeDomProvider __csharp = CodeDomProvider.CreateProvider("CSharp");
    WebReferenceOptions __webReferenceOptions = new WebReferenceOptions();
    __webReferenceOptions.Style = ServiceDescriptionImportStyle.Client;
    
    // Declares our custom SchemaImporterExtension.
    // It maps the wsdl document object type with the types 
    // associated with the supplied contract interface type.
    Type __schemaImporterExtensionGeneric = typeof(SchemaImporterExtension<>);
    Type __schemaImporterExtension = __schemaImporterExtensionGeneric.MakeGenericType(_contractType);
    __webReferenceOptions.SchemaImporterExtensions.Add(__schemaImporterExtension.AssemblyQualifiedName);
    
    // Build the CodeDOM tree for the proxy client associated with the specified wsdl document.
    ServiceDescriptionImporter.GenerateWebReferences(__wbRefs, __csharp, __cplUnit, __webReferenceOptions);

    Figure 2 – ServiceDescriptionImporter.GenerateWebReferences.

    Implémentation du contrat.

    Il reste une dernière étape avant de disposer d'un proxy fortement typé. En effet, le proxy généré suite à l'appel de la méthode GenerateWebReferences n'hérite que de la classe SoapHttpClientProtocol. Il est nécessaire d'adjoindre à cet héritage l'interface (TProxy) définisssant le contrat devant être rempli par le service web distant et donc par le proxy.

    // Make inheritance between the generated proxy client
    // and the specified contract type associated with the specified wsdl document.
    foreach (CodeTypeDeclaration codeTypeDeclaration in __namespace.Types)
    {
     if (codeTypeDeclaration.BaseTypes.Count > 0)
     {
       if (codeTypeDeclaration.BaseTypes[0].BaseType == typeof(SoapHttpClientProtocol).FullName)
       {
         codeTypeDeclaration.BaseTypes.Add(typeof(TProxy));
       }
     }
    } 
    

    Figure 3 – Inheritance between the generated proxy client and the specified contract.

    Conclusion

    En tirant profit des fonctionnalités avancées de la version 2.0 de la plateforme Microsoft .NET, il est possible de bénéficier dés à présent d'une expérience de développement similaire à celle que propose WCF pour communiquer avec des web services.

    Examples

    // Contracts
    public interface IContactManager
    {    
      Contact GetContact(Person person);
    }
    // Server Side
    [WebService(Namespace = "urn:RioterDecker.BluePrints")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    public class ContactWebService : WebService, IContactManager
    {
      [WebMethod]
      public Contact GetContact(Person person)
      {
        // Simulate the search of the contact associated with specified person.
        Contact __contact = new Contact();
        __contact.FirstName = person.FirstName;
        __contact.LastName = person.LastName;
        __contact.Age = person.Age;
        __contact.Phone = "+33 (0) 825 827 829";
        __contact.LastModifiedAt = DateTime.Now;
        return __contact;
      }
    }
    
    // Client Side
    static void Main(string[] args)
    {
      ProxyFactory<IContactManager> __proxyFactory = new ProxyFactory<IContactManager>(WSDL);
      IContactWebService __proxy = __proxyFactory.CreateProxy();
    
      Person __person = new Person();
      __person.FirstName = "Michel";
      __person.LastName = "DUGNARD";
      __person.Age = 47;
    
      Contact __contact = __proxy.GetContact(__person);
    }

    Résumé

    Vous trouverez, ci-dessous, la liste des concepts abordés par cet article :

    Pour aller plus loin

    Les exemples de code présentés dans cet article ont été extraits du projet ProxyFactory publié sur le portail communautaire CodePlex.

    Ce projet a pour fin de proposer une véritable librairie généralisant ce qui a été présenté dans cet article à différentes technologies de communication tel que Remoting ou WSE.

    Téléchargement

    Pré-requis NET 2.0

    Vous pouvez télécharger à partir du site du projet ProxyFactory : Du code source, un kit de démarrage mais également une vidéo de démonstration.

    La version pdf est disponible ici

This Blog

Syndication

Powered by Community Server (Personal Edition), by Telligent Systems