SSRS 2008 : Générer des rapports Reporting Services multi-langues (localisation)

by ygremillon 15. février 2012 20:13
Actuellement, le multi-langues dans un rapport Reporting Services peut être géré de plusieurs façons :
  • Créer un modèle de rapport par langue. C’est la méthode la plus simple, mais elle peut devenir très contraignante s’il y a un nombre importants de rapports / langues à traiter.
  • Gérer les libellés dans la base de données.
  • Utiliser la Globalisation / Localisation du Framework .NET via une librairie de ressources.
Nous allons nous intéresser à cette dernière méthode.
En premier lieu, nous allons créer la librairie de ressources.
Dans Visual Studio, nous créons un nouveau projet de librairie de classes.
On ajoute nos fichiers de ressources au projet. Pour mon exemple, le fichier de ressources se nomme : Localization.resx
On ajoute ensuite une nouvelle classe au projet, contenant le code ci-dessous :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;

    public static class LocalizedValue
    {
        public static string GetString(string cultureInfo, string resource, params object[] args)
        {
            CultureInfo ci = new CultureInfo(cultureInfo);
            string s = Localization.ResourceManager.GetString(resource, ci);
            if (args != null && args.Count() > 0)
                return string.Format(ci, s, args);
            else
                return s;
        }
    }
Une fois le projet compilé, il est nécessaire de copier l’ensemble des DLLs générées (le fichier de base et les fichiers de langues) sur le serveur SSRS.
Pour le designer (Visual Studio), les fichiers doivent être copiés dans le répertoire :
C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\PublicAssemblies
Pour le serveur de reporting, les fichiers doivent être copiés dans le répertoire :
C:\Program Files\Microsoft SQL Server\MSRS.10.MonServeur\Reporting Services\ReportServer\bin
Coté rapport, notre DLL doit être référencée pour pouvoir être utilisée.
Pour cela, dans le menu « Report », on sélectionne « Report Properties » puis l’onglet « References ». Enfin, on ajoute la DLL de ressources.
Ensuite, il suffit d’ajouter l’expression suivante dans une TextBox. 
=MyLibrary.LocalizedValue.GetString(User!Language, "MaRessource")
MyLibrary est l’espace de noms définit par ma DDL de ressources (par défaut, le nom de la DLL).
LocalizedValue est le nom de la classe statique que l’on souhaite utiliser.
GetString est le nom de la méthode que l’on a défini précédemment.
User!Language est une variable prédéfinie par SSRS. Il s’agit d’une chaine de caractères contenant le code de la culture (fr-FR, en-US, …)
MaRessource est la clé de ma ressource
 

Tags: ,

Développement

Développement applicative avec une base de donnée Oracle sans l'installation du client

by Charles BARJANSKY 7. novembre 2011 22:39
Le développement d'une application .Net pour un environnement Web ou Windows avec une plateforme Oracle était jusqu'à récemment problématique car il nécessitait l'installation du client Oracle sur l'environnement de développement et l'environnement finale (poste client et/ou serveur).
 
Dorénavant, la librairie Oracle ODP .NET requiert, au minimum, la présence des DLL suivantes dans l'environnement d'exécution de l'application:
  • Oracle.DataAccess.dll (ODP .NET)
  • OraOps11w.dll (DLL d'interface avec Instant Client)
  • oci.dll (Instant Client)
  • oraociicus11.dll (Instant Client)
Vous pouvez récupérer ces DLL dans l'archive "ODAC with Xcopy Deployment" sur le site d'Oracle.
Ainsi vous pouvez simplement ajouter ces fichiers dans le dossier d'exécution de l'application pour permettre le bon fonctionnement de celui-ci.

Je vous recommande bien entendu de "clarifier" ce processus. Il existe plusieurs façons d'y procéder et je vous propose la suivante:
  • Ajouter un dossier "Libraries" à votre solution
  • Copier les DLL citées ci-dessus
  • Ajouter les commandes suivantes à votre projet UI dans la liste des commandes à exécuter lors de la compilation (Project "Properties" -> "Build Events" tab -> "Post-build event command line" textbox)
xcopy "$(SolutionDir)Libraries\oci.dll" "$(TargetDir)"
xcopy "$(SolutionDir)Libraries\Oracle.DataAccess.dll" "$(TargetDir)"
xcopy "$(SolutionDir)Libraries\oraociicus11.dll" "$(TargetDir)"
xcopy "$(SolutionDir)Libraries\OraOps11w.dll" "$(TargetDir)"
Si vous avez un projet de package, veillez à ne pas les omettre de la liste des fichiers présents dans le dossier de l'application ("Application Folder" ou "Web Application Folder\bin").
 
Charles

Tags:

Développement

Lancer un processus asynchrone en Asp.Net

by Gilles DEHAIS 11. juillet 2011 20:46

Bonjour,

Nous allons voir comment lancer un processus de façon asynchrone en ASP.Net. En effet, vous vous êtes surement retrouver devant le cas d'une page qui doit lancer un processus relativement long (plusieurs minutes) et qui bloque alors la page Web en attendant que ce processus finisse et que le serveur réponde que l'opération s'est déroulée avec succès ou non.

Or, lorsque l'on clique sur le bouton qui lance ce processus sur le serveur, la page est comme "frisée" et les animations permettant d'indiquer à l'utilisateur d'attendre sont elles aussi figées. Heureusement, il existe un moyen de contourner ce problème : l'asynchrone.

Créer un site Asp.Net vide sous Visual Studio et ajoutez-y un autre page Web que l'on nommera "Default2.aspx". Nous allons maintenant éditer la page "Default.aspx" pour y ajouter du code javascript et nos éléments Web.

 

<title> Untitled Page </title> 
<script type="text/javascript"> 
 
    function ClientCallbackFunction(arg, ctx) {
        if (arg != "") {
            window.location = "Default2.aspx";
        }
    } 
</script>  
</head>


<body> 
 
<form id="form1" runat="server"> 
<div>
Async Callback
<br />
<asp:TextBox ID="tbxLongProcessArgs" runat="server" />
<br />
<input type="button" value="Start" id="bttAsync"
       onclick="javascript:document.all.Message.innerText='Process started';return DoTheCallback(document.all.tbxLongProcessArgs.value,1);" />
<br />
<span id="Message"></span>
</div>
</form>
</body>

 

Comme vous pouvez le constater, le bouton affiche le message 'Process started' et lance notre processus en mode asynchrone en lui donnant un argument lorsque l'utilisateur cliquera dessus. La fonction JavaScript 'ClientCallbackFunction' sera appelée lorsque notre processus sera terminé et permettra de passer sur notre 2ème page Web.  Maintenant nous allons éditer notre page Default2.aspx

 

<body>
    <form id="form1" runat="server">
    <div>
        <asp:TextBox ID="txtDesc" runat="server" Width="250px" TextMode="MultiLine"></asp:TextBox>
    </div>
    </form>
</body>

 

Cette page va nous permettre d'afficher le résultat de notre processus. Il ne nous reste plus qu'à ajouter le code behind qui lancera notre processus coûteux en temps en mode asynchrone

 

    protected void Page_Load(object sender, EventArgs e)
    {
        string js = Page.ClientScript.GetCallbackEventReference(this, "arg",
        "ClientCallbackFunction", "ctx", true);

        System.Text.StringBuilder sb = new System.Text.StringBuilder();
        sb.Append("function DoTheCallback(arg, ctx) {");
        sb.Append(js);
        sb.Append("}");
        Page.ClientScript.RegisterClientScriptBlock(this.GetType(),
        "callbackkey", sb.ToString(), true);
    }

Comme nous pouvons le voir, nous ajoutons à la page 'Default.aspx.cs' un évènement de type CallBack qui appelera notre fonction javascript 'ClientCallbackFunction' de notre page Default.aspx et ceci de manière totalement asynchrone. Cette évènement sera créé lorsque l'utilisateur cliquera sur le bouton 'Start' de notre page.

Maintenant, pour que la magie de l'asynchrone opère, il va falloir utiliser l'interface 'ICallbackEventHandler' que nous implémentons aussi dans notre code behind de la page Default.aspx.

 

    #region ICallbackEventHandler Members

    void ICallbackEventHandler.RaiseCallbackEvent(string eventArgument)
    {
        this._eventArgument = DoLongProcess(eventArgument);
    }

    string ICallbackEventHandler.GetCallbackResult()
    {
        if (!string.IsNullOrEmpty(this._eventArgument))
            Session["errorMessageImport_P"] = this._eventArgument;
        return this._eventArgument;
    }

    #endregion

 

Lorsque l'utilisateur clique sur le bouton 'Start', notre évènement asynchrone est créé et la méthode RaiseCallbackEvent est lancée. Nous avons aussi la possibilité de lui envoyer des paramètres. C'est cette méthode qui va lancer notre processus côuteux en temps. Une fois le processus fini, la méthode GetCallbackResult est lancée et appelle la méthode javascript 'ClientCallbackFunction' de notre page. Vous pouvez utiliser des variables de Session si vous désirez sauvegarder en mémoire sur le serveur le résultat du processus.

Ainsi, une fois notre processus terminé, la page 'Default2.aspx' est appelée par le client et la réponse retournée par notre processus est affichée à l'utilisateur. Vous pouvez remarquer que lorsque vous cliquez sur le bouton 'Start', la page n'est pas figé. Elle peut donc afficher un gif animé d'attente utilisateur par exemple. 

WebSite_Async.zip (5,03 kb)

Tags:

Développement

Workshop sur HTML5 chez Microsoft

by Emmanuel Humez 28. juin 2011 20:03

Le 16 mars 2011 a eu lien au centre de conférences Microsoft un workshop sur HTML5 / CSS3 / SVG 1.1
Cette conférence a eu pour but de faire un état des lieux actuel du HTML5, de son implémentation dans les dernières versions des principaux navigateurs, des bonnes pratiques offertes par des frameworks pour faire du progressive enhancement et du fallback, sur la plateforme ASP.NET.
Le but de cet article est de vous faire un résumé rapide, et de ce que j'ai retenu de plus intéressant. Il faut souligner que cet évènement a eu lieu tout de suite après la sortie en version finale d'Internet Explorer 9. Il est aussi un prolongement d'une session des TechDays 2011 sur le même sujet.

Une norme en cours de construction

D'abord, la spécification actuelle du HTML5 par le consortium W3C est au statut "working draft" jusqu'en mai 2011. Ce n'est qu'à cette date que l'en-cours des spécifications sera en version finale, et donnera le coup d'envoi de l'adoption officielle du standard. Au départ dans la roadmap du W3C, il était prévu de livrer la version finale du HTML5 en 2022 ! C'est pourquoi en parallèle s'est constitué un groupe de travail (le WHATWG) qui rassemble des pointures de l'internet (Apple, Google, Mozilla, ... mais pas Microsoft).

Le but est de faire évoluer la norme HTML et de faire pression sur le W3C pour accélérer la roadmap et de lui faire adopter certaines fonctionnalités en standard. Le WHATWG va plus loin en prônant un HTML non versionné et évolutif. De son côté,  le W3C a donc annoncé une version finale du HTML5 en 2014.

La guéguerre des navigateurs

Les spécifications en cours du HTML5 sont l'occasion d'une compétition entre les navigateurs (Safari, Chrome, Internet Explorer, Firefox), pour imposer des éléments non encore dans la norme HTML5. A ce titre, le navigateur qui respecte le plus la norme actuelle du W3C est Internet Explorer 9 (eh oui !), s'agissant des tests effectués. En fait, Chrome et Safari (basés sur le moteur Webkit) implémentent des fonctionnalités qui ne sont pas standardisés. Le but étant de faire adopter celles-ci par le consortium, bref d'imposer des éléments actuellement propriétaires (certains se souviennent de l'époque IE6 / Netscape 4.7).
Actuellement, le risque est grand pour les développeurs de devoir re-développer des fonctionnalités basés sur des techniques dont l'évolution est instable.

Un exemple sont les WebSockets permettant la notification entre le serveur et le client ; actuellement le protocole comporte une faille de sécurité, et il n'est pas sûr que les WebSockets soient conservées sous sa forme actuelle.

Quelques nouveautés du HTML5

Plus de balises sémantiques

Le HTML5 a pour but d'offrir des nouvelles balises sémantiques, adaptées à la structure des pages web : <header>, <footer>, <nav>, <article>, <section>, <aside>, <video>, <audio>plutôt que d'avoir des <div id="header">, <div id="nav">, etc.

La page doit comporter le doctype : <!DOCTYPE html> (Une page HTML doit toujours avoir un doctype, sinon le navigateur passe en mode Quirks !)

Les balises <audio> et <video> permettent de s'affranchir des plugins de lecture. Par contre, le codec n'est pas standardisé. De plus, la balise <video> ne supporte pas les DRM et le smooth streaming.

 
Pour <audio>, les codecs sont MP3, Vorbis (.ogg), AAC.
 
 
MP3
Vorbis
AAC
IE9
X
 
si extension .m4a ou .aac
Firefox 3.6
 
X
 
Chrome
X
X
si extension .m4a
 
Pour <video>, les codecs sont H264 (.mp4), WebM, Theora (.ogg).
 
 
H264
WebM
Theora
IE9
X
X*
 
Firefox 4
 
X
X
Chrome
 
X
X
Safari 5
X
   
Opera 11
 
X
X
* : téléchargement requis du codec
 

Les codecs ne sont pas pris en charge par tous les navigateurs. Par exemple, Firefox ne supporte pas H264 car il n'est pas ouvert (royalty free). Et Chrome prévoit de ne plus le supporter au profit de WebM. Bref, une guerre des codecs en perspective !

Pour le développeur, cela oblige à proposer à l'internaute un triple encodage de la vidéo et à faire du fallback.
Par exemple au sein de la balise <video>, on peut avoir une balise <object> pour charger un .xap (Silverlight), si le codec n'est pas pris en charge.

 
Exemple :
<video id="myVideo" controls autoplay>
  <source src="video.mp4" type="video/mp4; codecs='avc1.4201E,mp4a.40.2'" />
  <source src="video.ogg" type="video/ogg; codecs='theora,vorbis'" />
  <!-- insertion <object> silverlight -->
  <!-- insertion <object> flash -->
</video>

Un exemple plus poussé est donné sur le blog de Niall Kennedy, avec une détection JavaScript.

La balise <canvas> est une zone de dessin, manipulable avec des librairies JavaScript et les primitives, en initiant des contexts (méthode getContext()). Cette balise soulève un problème d'accessibilité car c'est une boite noire, et peut être très couteuse en terme de CPU. Il faut tirer parti de l'accélération matérielle que peut permettre le hardware de la machine (puce de la carte graphique). IE9 offre cette possibilité (voir le site Test Drive).

Les HTML5Forms (ou WebForms)

Attention, c'est toujours en working draft, et à ne pas confondre avec les webforms d'ASP.NET.
Les HTML5Forms ont été proposés par Opera, cela concerne des nouveaux types et attributs de la balise <input> afin de les rendre accessibles. Tous ces éléments ne sont pas tous pris en charge par les navigateurs. En fait, ces nouveautés existent déjà depuis quelques années (avec le web 2.0) en fallbacks avec des librairies AJAX, ou la librairie AjaxControlToolkit d'ASP.NET (gauge bar, calendar, etc.).

  • Attribut placeholder
    Il permet d'avoir un texte alternatif dans l'input lorsque celui-ci est vide et qu'il n'a pas le focus. Si le navigateur ne le prends en compte nativement, des fallbacks existent en jQuery, et plus anciennement avec le contrôle TextBoxWatermark de la librairie AjaxControlToolkit.
  • Attribut autofocus
    Permet d'avoir automatiquement le focus sur un input.
  • Et 13 nouveaux types (date, email, number, ..., etc.)

Quelques nouveautés du CSS3

On peut citer notamment, border-radius pour faire des coins arrondis, et box-shadow pour faire des ombres portées. background peut contenir un fichier *.svg. (un tag HTML ayant un box-shadow par dessus un background en SVG est supporté par IE9).
Des fallbacks existent si le navigateur ne les supporte pas, via des libraires JavaScript, ou en préfixant les instructions CSS en fonction des navigateurs (instructions propriétaires) :

 
<style type="text/css">
.rounded
{
    -moz-border-radius-topleft:3px; /* firefox */
    -moz-border-radius-topright:3px;
    -webkit-border-top-left-radius:3px; /* safari */
    -webkit-border-top-right-radius:3px;
    -khtml-border-top-left-radius:3px; /* konqueror*/
    -khtml-border-top-right-radius:3px;
    border-top-left-radius:3px; /* standard*/
    border-top-right-radius:3px;
}
</style>

Les Media Queries

Une fonctionnalité intéressante sont les Media Queries. Ils permettent d'adapter la résolution du client en fonction du poste client ou du device (smartphone, notebook, tablette), en chargeant des images plus petites. Ce qui évite l'implémentation d'un code JavaScript discriminant. La discrimination (ou device filtering) peut également se faire au chargement des fichiers CSS.

Exemple :
<link href="mobile.css" rel="Stylesheet" type="text/css" media="screen and (max-width:480px)" />
<link href="netbook.css" rel="Stylesheet" type="text/css" media="screen and (min-width:481px) and (max-width:1024px)" />
<link href="laptop.css" rel="Stylesheet" type="text/css" media="screen and (min-width:1025px)" />
On peut citer également les CSS3 animations, les CSS3 transitions mais sont pour l'instant en working draft et donc instables.

WOFF

Web Open Font Format est un wrapper des fonts au format True Type et Open Type. Il permet de télécharger des polices depuis le serveur (via CSS), de manière compressée (téléchargement plus rapide). Le WOFF est en cours de standardisation par le W3C et est soutenu par Microsoft, Mozilla, Opera. Il semble qu'il soit supporté par les dernières versions des navigateurs (sauf Safari actuellement).

SVG 1.1

Scalable Vector Graphics est un langage XML affichant des graphiques vectoriels (donc redimensionnables). Il est stylable par CSS, et est manipulable par JavaScript. SVG a l'avantage de l'accessibilité grâce aux propriétés aria ; mais a aussi l'inconvénient d'être très verbeux (c'est du XML) et peut être très lourd à charger si la page contient beaucoup de graphiques.
Typiquement, SVG est utilisé pour faire du charting. Un bon exemple est la librairie Highcharts JS. C'est une librairie JavaScript qui manipule des graphiques SVG et les rends interactifs.

WebGL

Est basé sur OpenGL, a pour but d'afficher de la 3D dans les pages web. Cette technologie est expérimentale et n'est pas pour l'instant un standard W3C.

Tests de conformité HTML5

Pour vérifier la conformité des pages web sur HTML5, il y a le validateur online du W3C. Le validateur vient d'intégrer dans son moteur le support du HTML5. Après avoir saisi l'URL d'un site, un rapport de test est généré affichant des erreurs et warnings :


 

En parallèle du test des pages web, il y a le test du navigateur pour vérifier si celui-ci intègre les fonctionnalités actuellement en standard, celles expérimentales (et celles propriétaires). Citons ACID3 (émanation du Web Standard Project) qui fait suite aux tests ACID2 et ACID1 ; et HTML5test. Mais ces tests font débat parmi les éditeurs et la communauté des développeurs, dans le contexte de compétition entre les navigateurs.

HTML5 sous Visual Studio

Des add-ons HTML5

La venue d'HTML5 a poussé la communauté ASP.NET à proposer des add-ons pour Visual Studio 2008/2010, mais pour l'instant c'est du ASP.NET MVC. D'ailleurs, MVC est "HTML compliant" et se prête bien à ça. Il n'est pas encore prévu d'avoir des WebControls ou HtmlControls encapsulant les nouvelles balises sur l'ASP.NET classique.
Ces add-ons sont visibles sur codeplex :

Il est possible également de créer un template pour Visual Studio, dans lequel les pages aspx générées auraient un squelette de code HTML5, mais cela mériterait un article sur ce sujet ;-)

Validation HTML5

Le SP1 de Visual Studio 2010 permet maintenant d'avoir un schéma de validation HTML5/XHTML5. Les nouvelles balises sont affichées avec l'intellisense. En revanche, la validation CSS3 n'est pas encore proposée.
Pour Visual Studio 2008, John Dyer a publié en 2009 un schéma de validation permettant d'avoir également l'intellisense.

Device filtering en ASP.NET classique

Une fonctionnalité bien pratique (et à mon avis peu conne) est de mettre en place des fichiers *.browser, dans le but de discriminer du code ou du contenu. Ce qui est affiché au end user est alors filtré en fonction de son navigateur client et/ou de son device (PC, smartphone, pocket PC). Au lieu de faire du test conditionnel en code behind via la propriété Request.Browser.Id, il est possible de le faire en déclaratif dans la page *.aspx.

Exemple de filtrage natif d'Internet Explorer et Firefox

Le device filtering peut s'effectuer sur des propriétés de contrôles HtmlControl ou WebControl, ou contrôles templatisés, ici en préfixant avec "ie" et "mozilla" :

 
<!-- Propriétés préfixées par ie ou mozilla -->
<asp:Label runat="server" ID="labelText"
    ie:Text="This is IE text"
    mozilla:Text="This is Firefox text"
    Text="This is general text"
/>
<br />
<asp:Button runat="server" ID="buttonText"
    ie:Text="IE Button"
    ie:OnClientClick="javascript:alert('Hello IE!');"
    mozilla:Text="FF Button"
    mozilla:OnClientClick="javascript:alert('Hello Firefox!');"
    Text="General Button"
    OnClientClick="javascript:alert('Hello everyone else!');"
/>
<br />
<asp:Menu runat="server" id="myMenu">
 
    <ie:Items>
       <asp:MenuItem Text="IE Item" />
    </ie:Items>
 
    <mozilla:Items>
       <asp:MenuItem Text="Firefox Item" />
    </mozilla:Items>
 
    <Items>
        <asp:MenuItem Text="Other Item" />
    </Items>
</asp:Menu>
 
Il peut s'effectuer également au niveau de certains attributs de la directive @page pour les pages *.aspx, ou la directive @control pour les usercontrols (voir une vue d'ensemble du device filtering en ASP.NET).
 
Cela peut être très utile lorsque on veut référencer une masterpage :
 
<%@ Page Language="C#"
    ie:MasterPageFile="~/MasterPageIE.master"
    mozilla:MasterPageFile="~/MasterPageFirefox.master"
    MasterPageFile="~/MasterPageGeneric.master"
%>

Comment cela fonctionne ?

Le filtrage en natif est basé sur des fichiers *.browser situés dans le répertoire C:\Windows\Microsoft.NET\Framework\<n° de version du fk>\Config\Browsers

 
 
Ce filtrage de base peut être surchargé par l'ajout d'un nouveau fichier *.browser dans une application ASP.NET.

Configurer le filtrage pour IE9, IE8, Firefox dans une application ASP.NET

A l'heure actuelle, le fichier ie.browser (situé dans C:\Windows\Microsoft.NET\Framework\<n° de version du fk>\Config\Browsers) ne contient pas (encore) de définition pour Internet Explorer 9. Il faut donc l'ajouter. Pour cela, il suffit d'ajouter à la racine de votre projet web un répertoire App_Browser (un répertoire réservé d'ASP.NET), lequel contiendra un nouveau fichier que vous nommerez ie9.browser.

 
 
Structure du fichier i9.browser :
 
<!--
You can find existing browser definitions at
<windir>\Microsoft.NET\Framework\<ver>\CONFIG\Browsers
-->
<browsers>
 
<browser id="IE9" parentID="IE6to9">
  <identification>
    <capability name="majorversion" match="9" />
  </identification>
  <capabilities>
    <capability name="jscriptversion" value="9.0" />
  </capabilities>
</browser>
 
</browsers>
 

La valeur de l'attribut parentID est une valeur existante dans le fichier ie.browser. La version du JScript pour IE9 a été reprise à partir des commentaires conditionnels (code côté client servant à discriminer la version d'Internet Explorer).

Pour illustrer le filtrage, nous allons mettre le code suivant dans la page Default.aspx :

 
<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true"
CodeBehind="Default.aspx.cs" Inherits="DeviceFiltering._Default" %>
 
<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
  <style type="text/css">
    label{ margin:0 1em 0 0; }
  </style>
</asp:Content>
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
  <h2>Browser filtering in ASP.NET</h2>
 
  <!-- filtrage réalisé en code behind -->
  <div id="divIE9" runat="server">
    <asp:Label id="lblIE9" runat="server" AssociatedControlID="txtNameIE9">Votre nom (sous IE9)</asp:Label>
    <input id="txtNameIE9" runat="server" type="text" />
  </div>
  <div id="divIE8" runat="server">
    <asp:Label id="lblIE8" runat="server" AssociatedControlID="txtNameIE8">Votre nom (sous IE8)</asp:Label>
    <input id="txtNameIE8" runat="server" type="text" />
  </div>
  <div id="divFF" runat="server">
    <asp:Label id="lblFF" runat="server" AssociatedControlID="txtNameFF">Votre nom (sous Firefox)</asp:Label>
    <input id="txtNameFF" runat="server" type="text" autofocus="true" placeholder="Entrez votre nom" />
  </div>
  <div id="divBrowser" runat="server">
    <asp:Label id="Label1" runat="server" AssociatedControlID="txtName">Votre nom</asp:Label>
    <input id="txtName" runat="server" type="text" />
  </div>
 
  <!-- filtrage réalisé en déclaratif -->
  <p>
    <asp:Button ID="btnTest" runat="server"
         ie9:OnClientClick="javascript:alert('IE9 button!')"
         ie8:OnClientClick="javascript:alert('IE8 button!')"
         firefox35:OnClientClick="javascript:alert('Firefox button!')"
         ie9:Text="I'm a button for IE9"
         ie8:Text="I'm a button for IE8"
         firefox35:Text="I'm a button for Firefox" />
  </p>
 
</asp:Content>
 
Et le code suivant dans la page Default.aspx.cs :
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
 
namespace DeviceFiltering
{
    public partial class _Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!this.IsPostBack)
            {
                divIE8.Visible = false;
                divIE9.Visible = false;
                divFF.Visible = false;
                divBrowser.Visible = false;
 
                switch (Request.Browser.Id)
                {
                    case "ie8":
                        divIE8.Visible = true;
                        break;
                    case "ie9":
                        divIE9.Visible = true;
                        break;
                    case "firefox35":
                        divFF.Visible = true;
                        break;
                    default:
                        divBrowser.Visible = true;
                        break;
 
                }
 
            }
        }
    }
}
 
Résultat sous IE9 :
 
 
Résultat sous IE9 en mode IE8 :
 
 
Résultat sous Firefox :
 

On peut donc remarquer que l'affichage du bouton se fait de manière déclarative, en préfixant un ID de browser sur un attribut et c'est fini ! :)

Conclusion

Le HTML5 est d'ores et déjà un progrès concernant la sémantique et l'accessibilité des pages web. Ses spécifications auront l'avantage de rendre natives des fonctionnalités telles que les webforms (actuellement rendues avec des librairies AJAX/JavaScript).
Mais attention au buzz marketing effectué autour, et à la dépendance des navigateurs. Car ceux-ci ne l'implémentent pas de la même manière, les performances peuvent être assez différentes d'un device à l'autre. Le HTML5 n'est absolument pas pour l'instant synonyme de développement unique !

Par ailleurs, d'autres briques du HTML5 n'ont pas été évoquées dans ce workshop. Citons le Web Storage (stockage de données dans le navigateur, cache d'application), le Web SQL Database (basé sur SQLite), les Web Workers (scripts JS tournant en tâche de fond), la géolocalisation (via une API JavaScript), les microdata (amélioration du référencement naturel par ajout de données dans les balises, lues uniquement par les robots).

A l'heure de l'expansion imminente du HTML5 sur les sites web et mobile, gageons que les futurs add-ons ou CTP d'ASP.NET apporteront leur lot de nouveauté !

Pour aller plus loin

 

Tags: , , ,

Développement | Evènement

La première appliance Microsoft Windows Azure débarque en août 2011

by Philippe Limantour 12. juin 2011 19:27

Microsoft et Fujitsu annoncent la première appliance Windows Azure pour ce mois d'août, en test auprès d'une dizaine de sociétés depuis avril.

L'appliance permet à une société de s'équiper chez elle d'une plateforme Windows Azure afin de stocker ses données et ses programmes sur son site plutôt que dans l'un des vingt datacenters Microsoft de part le Monde. Cette appliance est particulièrement interessante pour les entreprises qui ont de très grandes quantités de données à traiter, évitant des transits réseaux. La plateforme Windows Azure accueille un nombre croissant de solutions : http://www.microsoft.com/windowsazure/evidence/

Tags: , ,

Cloud | Développement | Infrastructure | Innovation

Développez sur iOS, Windows Phone 7 et Android grâce à Windows Azure

by Philippe Limantour 9. mai 2011 20:05

Microsoft vient de mettre à disposition le toolkit Windows Azure pour iOS

http://www.wadewegner.com/2011/05/windows-azure-toolkit-for-ios/

Tags:

Cloud | Développement

Entity Framework 4 et l'extensibilité

by Stéphane FICO 28. février 2011 01:22

Dans le cadre de différentes missions j’ai été amené à mettre en place des solutions reposant sur l’ORM de Microsoft Entity Framework 4. Dans ce cadre j’ai été confronté à différentes problématiques autour de la génération du code .Net et DDL. Je vous propose donc d’explorer ensemble ce sujet au travers d'une série d’articles.

Dans ce premier article je voudrais vous présenter l’extensibilité de l’Entity data model designer (que je nommerais designer dans la suite de cet article).

L’idée générale est de vous présenter une solution permettant d’étendre les capacités d’EF pour lui ajouter la capacité de générer des contraintes d’unicités au niveau de la base de données.

Cette solution va se décomposer en deux parties, à savoir une extension au niveau du designer, puis une seconde extension au niveau du Template T4 de génération du code DDL.

Dans ce premier article je vais me concentrer sur le designer.

Le contexte étant posé passons à la pratique…

Extension du designer

L'extensibilité au sein du designer s’appuie sur le Framework MEF (Managed Extensibility Framework). Au travers de ses API de composition, MEF va nous permettre d'étendre les fonctionnalités du designer aussi bien au niveau des propriétés, des entités, ou des liaisons inter-entités.

Dans notre cas l’objectif consiste simplement à ajouter une propriété de type Booléenne (nommée [IsUnique]) qui sera disponible dans la fenêtre des propriétés de Visual Studio lors de la sélection d’une propriété sur une entité.

La fenêtre des propriétés de Visual Studio ressemblera à celle-ci :

image

Le designer EF s’appuient sur le fichier [.edmx] pour stocker les méta-données, ce fichier est un fichier au format XML découpé en quatre parties :

  • CSDL (Conceptual schema definition language), décrit les objets (entités, relations, …).
  • SSDL (store schema definition language), décrit le stockage (table, colonnes, …).
  • MSL (mapping specification language), défini le mapping.

Une dernière partie Designer qui contient le positionnement des éléments visuel.

Dans notre cas seul le CSDL va évoluer, chaque propriété d’une entité va se voir ajouter un nœud [Constraints] pourvu d’un attribut [IsUnique].

Pour illustrer notre propos partons du modèle suivant :

image

Une simple classe [Users] avec un identifiant auto-généré et une propriété Logon sur lequel il faut positionner une contrainte d’unicité.

Le CSDL standard pour cette entité correspond à l’extrait de code suivant :

<edmx:ConceptualModels>
    <Schema xmlns="http://schemas.microsoft.com/ado/2008/09/edm" xmlns:cg="http://schemas.microsoft.com/ado/2006/04/codegeneration" xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator" Namespace="MyModel" Alias="Self" xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation">
        <EntityContainer Name="MyModelContainer" annotation:LazyLoadingEnabled="true">
            <EntitySet Name="UsersSet" EntityType="MyModel.Users" />
        </EntityContainer>
        <EntityType Name="Users">
            <Key>
                <PropertyRef Name="Id" />
            </Key>
            <Property Type="Int32" Name="Id" Nullable="false" annotation:StoreGeneratedPattern="Identity" />
            <Property Type="String" Name="Logon" Nullable="false" />
            <Property Type="String" Name="FirstName" Nullable="false" />
            <Property Type="String" Name="LastName" Nullable="false" />
            <Property Type="DateTime" Name="BirthDate" Nullable="false" />
        </EntityType>
    </Schema>
</edmx:ConceptualModels>

Après évolution il faudra qu’il corresponde à l’extrait de code suivant :

<edmx:ConceptualModels>
    <Schema xmlns="http://schemas.microsoft.com/ado/2008/09/edm" xmlns:cg="http://schemas.microsoft.com/ado/2006/04/codegeneration" xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator" Namespace="MyModel" Alias="Self" xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation">
    <EntityContainer Name="MyModelContainer" annotation:LazyLoadingEnabled="true">
        <EntitySet Name="UsersSet" EntityType="MyModel.Users" />
    </EntityContainer>
    <EntityType Name="Users">
        <Key>
        <PropertyRef Name="Id" />
        </Key>
        <Property Type="Int32" Name="Id" Nullable="false" annotation:StoreGeneratedPattern="Identity" />
        <Property Type="String" Name="Logon" Nullable="false"><neos-sdi:Constraints xmlns:neos-sdi="http://schemas.neos-sdi.com/EntityFramework/2010/07" neos-sdi:IsUnique="true" /></Property>
        <Property Type="String" Name="FirstName" Nullable="false" />
        <Property Type="String" Name="LastName" Nullable="false" />
        <Property Type="DateTime" Name="BirthDate" Nullable="false" />
    </EntityType>
    </Schema>
</edmx:ConceptualModels>

Assez de théorie passons à la pratique.

Commencer par créer un projet de type ClassLibrary.

Ajouter une classe UnicityPropertyExtensionFactory qui implémente l’interface : IEntityDesignerExtendedProperty.

Cette interface permet d’ajouter des propriétés sur les objets visuel du designer ou du model browser.

Voici le code de la classe :

[Export(typeof(IEntityDesignerExtendedProperty))]
[EntityDesignerExtendedProperty(EntityDesignerSelection.ConceptualModelProperty)]
[PartCreationPolicy(CreationPolicy.NonShared)]
internal class UnicityPropertyExtensionFactory : IEntityDesignerExtendedProperty
{
    public object CreateProperty(XElement element, PropertyExtensionContext context)
    {
        object result = null;
        result = new UnicityPropertyExtension(element, context);
        return result;
    }
}

Attardons-nous un peu sur les attributs qui décorent cette classe :

  • [Export(typeof(IEntityDesignerExtendedProperty))] Attribut définissant le type d’export MEF.
  • [EntityDesignerExtendedProperty(EntityDesignerSelection.ConceptualModelProperty)] Attribut définissant le type d’objet pour lequel cette propriété sera disponible, dans notre cas la propriété ne sera présente que pour les objets de type Property.
  • [PartCreationPolicy(CreationPolicy.NonShared)] Indique à MEF que chaque instanciation de ce type créer une nouvelle instance de la classe.

Maintenant ajouter une seconde classe nommée UnicityPropertyExtension, qui va nous permettre de lire et écrire dans le fichier [.edmx].

Comme nous pouvons le voir dans l’extrait de code ci-dessous, cette classe contient une seule propriété, qui est annoté de quelques attributs qui définissent son nom, sa catégorie, sa description et sa valeur par défaut. Toutes ces informations seront disponibles au sein de la fenêtre des propriétés de Visual Studio.

[DisplayName("IsUnique"),
Category("Neos"),
Description("Indicate if a Unique SQL constraint must be applied."),
DefaultValue(false)]
public bool IsUnique
{
    get
    {
        return this.isUnique;
    }
 
    set
    {
        this.isUnique = value;
        this.StoreValue();
    }
}

Passons ensuite au constructeur, c’est dans ce constructeur que la valeur de l’attribut [IsUnique] sera lue, si elle existe dans le modèle (dans le fichier [.edmx]).

public UnicityPropertyExtension(XElement xElement, PropertyExtensionContext context)
{
    this.context = context;
    this.xmlProperty = xElement;
 
    if (!object.ReferenceEquals(xElement, null))
    {
        /// Retrieve values from the .edmx file, try to get the Constraints element
        XElement content = this.xmlProperty.Element(UnicityPropertyExtension.ConstraintsElement);
        if (!object.ReferenceEquals(content, null))
        {
            XAttribute xAttribute = xElement.Attribute(UnicityPropertyExtension.IsUniqueAttribute);
            if (!object.ReferenceEquals(xAttribute, null))
            {
                bool.TryParse(xAttribute.Value, out this.isUnique);
            }
        }
    }
}

Pour terminer voyons la méthode [StoreValue], qui est en charge du stockage de la valeur de la propriété [IsUnique] au sein du modèle.

public void StoreValue()
{
    this.mutex.WaitOne();
    if (!object.ReferenceEquals(this.context, null))
    {
        try
        {
            // Create a modify scope for Undo / Redo
            using (EntityDesignerChangeScope scope = this.context.CreateChangeScope("Neos Constraints Scope"))
            {
                // Try to get the Constraints elment
                XElement constraintsElement = this.xmlProperty.Element(UnicityPropertyExtension.ConstraintsElement);
                if (!object.ReferenceEquals(constraintsElement, null))
                {
                    constraintsElement.RemoveNodes();
                }
                else // Create the Constraints Element
                {
                    constraintsElement = new XElement(UnicityPropertyExtension.ConstraintsElement, UnicityPropertyExtension.NamespaceAttribute);
                    this.xmlProperty.Add(constraintsElement);
                }
 
                // Set the IsUnique Attribute value
                var isUniqueAttribute = constraintsElement.Attribute(UnicityPropertyExtension.IsUniqueAttribute);
                if (object.ReferenceEquals(isUniqueAttribute, null))
                {
                    isUniqueAttribute = new XAttribute(UnicityPropertyExtension.IsUniqueAttribute, this.IsUnique);
                    constraintsElement.Add(isUniqueAttribute);
                }
                else
                {
                    isUniqueAttribute.Value = this.IsUnique.ToString();
                }
 
                scope.Complete();
            }
        }
        catch (Exception exception)
        {
            System.Diagnostics.Debug.WriteLine(exception.StackTrace);
        }
    }
 
    this.mutex.ReleaseMutex();
}

Vous aurez remarquer l’utilisation de la classe Mutex pour garantir qu’un seul Thread n’accèdera à cette section de code à la fois, ainsi que l’utilisation d’un EntityDesignerChangeScope qui va stocker les action effectuées, et ainsi permettre l’utilisation classique du Undo/Redo de Visual Studio.

Création du package de déploiement

Ajouter un nouveau projet de type Visual Studio Package. Ce projet va permettre de déployer l’extension. La création d’un nouveau package s’effectue au travers d’un Wizard dont voici les captures d’écran.

imageimage

imageimage

image

Editer le fichier [source.extension.vsixmanifest], puis ajout le projet [Neos.EF.Designer.Extensions] au package.

image

Lancer la compilation et l’exécution des tests unitaires, si tout se passe bien il ne reste plus qu’à installer l’extension en exécutant le fichier [Neos.EF.Extensions.Package.vsix].

imageimage

Création d’un projet de tests

Ajouter un nouveau projet de type Console Application. Ce projet va permettre de vérifier que le package est bien déployé et opérationnel. La création d’un nouveau package s’effectue au travers d’un Wizard dont voici les captures d’écran.

Ajouter un nouvel item au projet de type ADO.NET Entity Data Model nommé MyModel basé sur un modèle vide.

Ajouter ensuite une entité Users, et lui définir quelques propriétés.

Sélectionner une propriété et vérifier que l’attribut [IsUnique] est bien présent.

image

Il ne reste plus qu’à positionner la propriété [IsUnique] à true et Vérifier que le fichier [.edmx] est bien mis à jour.

Pour ce faire ouvrir le fichier [.edmx] avec le XML (Text) Editor, rechercher un attribut [neos-sdi:IsUnique] et valider que sa valeur est positionnée à true comme dans l’exemple de code suivant :

<edmx:ConceptualModels> 
    <Schema xmlns="http://schemas.microsoft.com/ado/2008/09/edm" xmlns:cg="http://schemas.microsoft.com/ado/2006/04/codegeneration" xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator" Namespace="MyModel" Alias="Self" xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation"> 
    <EntityContainer Name="MyModelContainer" annotation:LazyLoadingEnabled="true"> 
        <EntitySet Name="UsersSet" EntityType="MyModel.Users" /> 
    </EntityContainer> 
    <EntityType Name="Users"> 
        <Key> 
        <PropertyRef Name="Id" /> 
        </Key> 
        <Property Type="Int32" Name="Id" Nullable="false" annotation:StoreGeneratedPattern="Identity" /> 
        <Property Type="String" Name="Logon" Nullable="false" > 
            <neos-sdi:Constraints xmlns:neos-sdi="http://schemas.neos-sdi.com/EntityFramework/2010/07" neos-sdi:IsUnique="true" /> 
        </Property> 
        <Property Type="String" Name="FirstName" Nullable="false" /> 
        <Property Type="String" Name="LastName" Nullable="false" /> 
        <Property Type="DateTime" Name="BirthDate" Nullable="false" /> 
    </EntityType> 
    </Schema> 
</edmx:ConceptualModels> 

Voici qui clôture ce premier article, dans les prochains jours j’aborderai la génération du DDL.

La solution est accessible ici : Neos.EF.Extensions.Package.zip (565,25 kb)

Enjoy :-)

Tags: , , ,

Développement

Le pattern MVVM en WPF

by Gilles DEHAIS 24. février 2011 20:35

Grâce à cet article, nous allons voir comment lier nos objets graphiques (UI) avec notre code métier en WPF en s’appuyant le modèle MVVM (Model View ViewModel). Commençons par créer une nouvelle application WPF via Visual Studio. Ouvrez le fichier nommé “Window1.xaml”. Nous allons créer notre petite interface graphique avec quelques éléments graphiques dans une simple grille.

<Window x:Class="WpfMVVM.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Binding in WPF" Height="300" Width="300">
    <Grid Margin="5">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="0,3*" />
            <ColumnDefinition Width="0,7*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="100" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <TextBlock Grid.Column="0" Grid.Row="0" Text="First name :" />
        <TextBlock Grid.Column="0" Grid.Row="1" Text="Last name :" />
        <TextBox Grid.Column="1" Grid.Row="0" Text="{Binding Path=Client.FirstName,
UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Margin="0,0,0,10" /> <TextBox Grid.Column="1" Grid.Row="1" Text="{Binding Path=Client.LastName,
UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Margin="0,0,0,10" /> <TextBlock Grid.Column="0" Grid.Row="3" Text="My client is :" /> <TextBlock Grid.Column="1" Grid.Row="3" Text="{Binding Path=Client.FullName,
Mode=OneWay}" /> </Grid> </Window>

Comme vous pouvez le voir, nous avons disposé 3 textbox. Chacune de ces textbox ont un champ “Text” que nous avons bindé à une propriété de notre futur objet Client. Ainsi, nous pourrons renseigner le prénom, le nom et récupérer le nom complet du client dans notre interface graphique.

De plus, nous utilisons l’option “UpdateSourceTrigger” pour mettre à jour nos propriétés dès que l’utilisateur modifie un de ces champs. Sans cette option, le champ est mis à jour uniquement lorsque l’utilisateur quitte le champ modifié. Le mode “TwoWay” permet de lier dans les deux sens notre objet graphique, soit le get et le set de notre propriété. En mode “OneWay”, seul le get de notre propriété est utilisé et donc notre propriété n’est pas modifiable via l’interface graphique.

Nous allons maintenant créer notre objet Client.

public class Client : ViewModelBase
{
    private string _fistName;
    public string FirstName
    {
        get
        {
            return _fistName;
        }
        set
        {
            _fistName = value;
            RaisePropertyChanged("FirstName");
            RaisePropertyChanged("FullName");
        }
    }

    private string _lastName;
    public string LastName
    {
        get
        {
            return _lastName;
        }
        set
        {
            _lastName = value;
            RaisePropertyChanged("LastName");
            RaisePropertyChanged("FullName");
        }
    }

    public string FullName
    {
        get
        {
            return _fistName + " " + _lastName;
        }
    }

    public Client()
    { }
}

Nous avons donc défini nos 3 propriétés liées (bindées) à l’interface graphique. Concentrons nous sur la méthode “RaisePropertyChanged”. En effet, pour alerter notre UI que l’une de nos propriétés à été modifiée, il faut utiliser l’interface “INotifyPropertyChanged”. Ainsi, si notre code métier modifie le prénom de notre client, il faut avertir la UI de rafraichir notre champ “prénom” mais aussi le champ “Nom complet”. On implémente donc notre classe Client par une classe “ViewModelBase”.

public class ViewModelBase : INotifyPropertyChanged
    {
        #region INotifyPropertyChanged Membres
        /// <summary>
        /// Basic implementation of INotifyPropertyChanged
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Raises PropertyChanged event
        /// </summary>
        /// <param name="propertyName">name of the property which has changed</param>
        protected virtual void RaisePropertyChanged(string propertyName)
        {
            VerifyPropertyName(propertyName);
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        protected void VerifyPropertyName(string propertyName)
        {
            if (string.IsNullOrEmpty(propertyName)) return;
            // Verify that the property name matches a real,  
            // public, instance property on this object.
            if (TypeDescriptor.GetProperties(this)[propertyName] == null)
            {
                Debug.Fail(string.Format("Invalid property name '{0}' in" +
"OnPropertyChanged call"
, propertyName)); } } #endregion }

Cette classe “ViewModelBase” implémente INotifyPropertyChanged. Je n’irai pas plus loin dans l’explication d’INotifyPropertyChanged car cela n’est pas le but de cet article, mais sachez que ceci permet d’alerter notre UI que nos objets métiers ont été modifiés.

Il nous reste encore une dernière classe à créer, celle qui regroupe les objets qui doivent être affichés dans la UI. Elle contient donc notre objet Client et le constructeur qui permet la création d’un client que l’on affichera dans notre UI.

public class ClientViewModel
    {
        public Client Client
        {
            get;
            set;
        }

        public ClientViewModel()
        {
            Client = new Client() { FirstName = "Bill", LastName = "Smith" };
        }
    }

Pour finir, aller dans le code-behind de la page WPF de notre application. Il faut ici définir ce qu’on appelle le “DataContext” de notre page. Ainsi, on lie notre classe “ClientViewModel” avec la page graphique de notre application. Lorsque la page va être créée, son contexte va être rattaché à notre classe, qui contient tous les objets métiers que l’on souhaite afficher sur notre page.

public partial class Window1 : Window
    {
        ClientViewModel _viewModel;

        public Window1()
        {
            InitializeComponent();
            _viewModel = new ClientViewModel();
            this.DataContext = _viewModel;
        }
    }

Appuyer sur F5 dans Visual Studio. La fenêtre WPF apparait et vous pouvez voir que nos textbox sont automatiquement remplies par le prénom, nom et nom complet du client que nous avons créé dans notre ViewModel (ClientViewModel). Si vous redéfinissez le prénom et/ou le nom du client dans l’interface graphique, notre objet métier client est alors automatiquement mis à jour et son nom complet est rafraichit dans l’interface automatiquement.

Ainsi, grâce au pattern MVVM que nous venons de voir, vous n’avez plus à vous préoccuper de la mis à jour de vos objets métiers par rapport à votre interface graphique et vice-versa. Cela rend votre application beaucoup plus flexible et plus robuste.

WpfMVVM.zip (57,84 kb)

Tags: , ,

Développement

VSTO : Utilisation du ruban Office sous Excel

by Gilles DEHAIS 22. février 2011 02:09

Le ruban d’action office se présente sous la forme d’un panneau latéral, par défaut à droite, et qui permet de rajouter des actions personnalisées. Ainsi, nous pouvons rajouter à Excel, Word… nos propres UI. Ainsi, des actions répétitives peuvent être codées en VSTO et permettre à l’utilisateur de cliquer sur un bouton et d’insérer dans une feuille Excel des données provenant d’une base de données SQL, par exemple.

Le problème ici, se situe dans le fait que l’utilisateur peut ouvrir plusieurs Excel, plusieurs classeurs Excel contenant plusieurs feuilles. Or, dans certains cas, l’affichage de notre UI dans le panneau d’actions doit différer suivant le fichier Excel ouvert ou la feuille Excel en cours d’édition.

Pour cela, il va falloir s’appuyer sur le nom de la feuille Excel. Or ce nom peut être changer par l’utilisateur. Il faut donc récupérer son nom de code, qui n’est accessible que si le VBA est exécutable sous Excel. Voici la méthode à suivre pour activer ce code VBA, récupérer le nom de code de la feuille et afficher la bonne UI suivant la feuille active dans le classeur Excel.

Tout d’abord, il faut commencer par afficher le panneau d’actions et lui lier un User Control qui hébergera notre UI.

   1:  /// <summary>
   2:  /// Create the task panel
   3:  /// </summary>
   4:  public void CreateTaskPanel()
   5:  {
   6:       // Création de notre panneau UI
   7:       _taskPaneControl = new TaskPaneControl();
   8:       // Assigne le panneau a notre panneau d'action Office, ici celui d'Excel
   9:       _customTaskPane = Globals.ThisAddIn.CustomTaskPanes.Add(_taskPaneControl,
                                                                    "My panel control");
  10:       _customTaskPane.Width = 350;
  11:       _customTaskPane.Visible = true;
  12:       //Récupère la version d'Excel
  13:       string version =
                     Globals.ThisAddIn.Application.GetType().InvokeMember("Version",
                     System.Reflection.BindingFlags.GetProperty, null,
                     Globals.ThisAddIn.Application, null,
                     new CultureInfo("en-US")).ToString();
  14:       SetVBTrusting(version);
  15:  }

Nous allons maintenant voir comment rendre exécutable du VBA sous Excel. Pour cela, il va falloir attaquer le registre de Windows pour créer une clé si elle n’existe pas et lui passer une valeur “1”. Voici comment faire :

   1:  /// <summary>
   2:  /// Customise le registre Windows pour permettre l'exécution VBA sous Office.
   3:  /// </summary>
   4:  /// <param name="excelVersion">Excel version</param>
   5:  public static void SetVBTrusting(string excelVersion)
   6:  {
   7:       if (string.IsNullOrEmpty(excelVersion))
                   throw new ArgumentNullException("excelVersion");
   8:       string keyPath = string.Format(
@"Software\Microsoft\Office\{0}\Excel\Security", excelVersion);
   9:       RegistryKey key = null;
  10:       try
  11:       {
  12:             key = Registry.CurrentUser.OpenSubKey(keyPath, true);
  13:             if (key != null)
  14:                 key.SetValue("AccessVBOM", 1);
  15:             else
  16:                 throw new Exception("La clé VBOM ne peut être trouvé.");
  17:        }
  18:        catch (SecurityException e)
  19:        {
  20:             throw new Exception("La clé VBOM ne peut être mis a jour.", e);
  21:        }
  22:        finally
  23:        {
  24:            if (key != null)
  25:            {
  26:                  key.Close();
  27:            }
  28:        }
  29:  }

La ligne 12 permet de tenter de récupérer la clé “VBOM” sur laquelle se base Excel pour rendre exécutable du code VBA, puis la ligne 14 rend cette fonction utilisable.

Enfin, nous avons rajouter dans notre UI un simple label qui va nous permettre de rendre visible le nom de code de la feuille Excel en cours d’exécution. A chaque changement de feuille, il faut donc rafraichir ce label. Voici comment procédé :

   1:  /// <summary>
   2:  /// Rafraichit notre UI dans le panneau de control
   3:  /// </summary>
   4:  /// <param name="currentSheet">Feuille Excel en cours d édition</param>
   5:  private void RefreshTaskPanel(Excel.Worksheet currentSheet)
   6:  {
   7:        if (currentSheet != null)
   8:        {
   9:             string codeName = 
                  currentSheet.Application.ActiveWorkbook.VBProject.VBComponents.Item
                  (currentSheet.CodeName).Name;
  10:             _taskPaneControl.lblSheetName.Text = codeName;
  11:        }
  12:   }

Bien sûr, toutes ces méthodes doivent être appelées dans les bons évènements d’Excel tel que l’évènement d’activation de la feuille Excel.

La capture d’écran suivante montre bien que notre panneau d’action appelé “My panel control”, qui contient notre User Control qui comprend lui même notre label, affiche bien le nom de code de la première feuille, qui est “Feuil1”, alors que l’utilisateur à renommer cette feuille “Tableau1”.

ResultatFinal

Vous pouvez donc maintenant créer une UI différentes suivant la feuille sélectionnée par l’utilisateur, rendant ainsi votre AddIn plus flexible aux exigences de vos clients. Vous trouverez, joins à cet article, tout le code source.

VSTOTaskPane.zip (85,12 kb)

Tags: ,

Développement

VSTO : Les formules et les formats sous Office

by Gilles DEHAIS 20. février 2011 18:23

Lors de l’utilisation de VSTO (Visual Studio Tools for Office), le développeur ne sait jamais à l’avance sur quelle langue l’Office 2007, ou plus récemment 2010, est utilisée par l’utilisateur final. Cela peut entrainer des bugs ou des formats erronés lors de l’utilisation du complément Office que vous avez développé.

Pour éviter tout problème, il est crucial de lier sont code à une culture précise et neutre pour chaque office disponible sur le marché. De base, la culture neutre est la culture “en-US”. Nous allons donc l’utiliser pour rentrer nos formules sous Excel, par exemple.

   1:  //Récupération de la culture utilisateur.
   2:  CultureInfo currentCulture = Thread.CurrentThread.CurrentCulture;
   3:  try
   4:  {
   5:          //Définition de la culture en-Us pour notre thread Excel
   6:          Thread.CurrentThread.CurrentCulture = new CultureInfo("en-Us");
   7:          Excel.Worksheet sheet = (Excel.Worksheet)Sh;
   8:          sheet.get_Range("A1", "A1").Value2 = "10";
   9:          sheet.get_Range("A2", "A2").Value2 = "3";
  10:          //Création de la formule sur la cellule A3
  11:          sheet.get_Range("A3", "A3").Formula = "=SUM(A1:A2)";
  12:  }
  13:  finally
  14:  {
  15:          //Re-définition de la culture utilisateur pour notre thread Excel
  16:          Thread.CurrentThread.CurrentCulture = currentCulture;
  17:  }

Comme nous pouvons le voir, les lignes 8 et 9 nous permettent de remplir réciproquement les cellules A1 et A2 avec les valeurs 10 et 3. La ligne 11 permet d’associer la cellule A3 à notre formule de somme entre les cellules A1 et A2. La valeur qui s’inscrit alors dans notre cellule A3 est donc 13.

La capture d’écran suivante nous montre bien que, même si notre application est installée sur un Excel français, la somme s’effectue bien et d’ailleurs, la formule affichée n’est pas “=SUM(A1:A2)” mais bien la formule traduite en français, soit “=SOMME(A1:A2)”.

 

SUM_thumb[5]_thumb

 

Ceci va de même pour les formats que l’on souhaite appliqués sur une cellule ou un groupe de cellule, nommé “Range”. En effet, en anglais, le séparateur de milliers est la virgule et le séparateur de décimal est le point. En français, le séparateur de milliers est l’espace et le séparateur de décimal est la virgule. Si vous ne faites pas comme précédemment, vous aurez des formats aberrent suivant la langue de l’Office installé sur le poste utilisateur.

   1:  //Récupération de la culture utilisateur.
   2:  CultureInfo currentCulture = Thread.CurrentThread.CurrentCulture;
   3:  try
   4:  {
   5:        //Définition de la culture en-Us pour notre thread Excel
   6:        Thread.CurrentThread.CurrentCulture = new CultureInfo("en-Us");
   7:        Excel.Worksheet sheet = (Excel.Worksheet)Sh;
   8:        Excel.Range range = sheet.get_Range("A1", "A3");
   9:        //Création du format avec 3 chiffres après le séparateur de décimal
  10:        range.NumberFormat = "###,###,##0.000";
  11:  }
  12:  finally
  13:  {
  14:        //Re-définition de la culture utilisateur pour notre thread Excel
  15:        Thread.CurrentThread.CurrentCulture = currentCulture;
  16:  }

La ligne 10 permet de définir le format tel qu’il devrait apparaitre pour une version US d’Office car nous nous avons établit la culture d’Excel en “en-US” qui est la version neutre. Ainsi, sur un Excel français, l’affichage sera bien rétablit pour la langue française.

 

FORMAT_thumb[2]_thumb

 

Vous savez maintenant rendre votre complément compatible avec n’importe quelle version d’Office.

Tags:

Développement

Neos-SDI  Neos-SDI