mercredi 14 septembre 2011

Désormais, le contenu de ce blog est accéssible sur: http://lusiasiano.blogspot.com.


jeudi 21 octobre 2010

Argument de publication ou de rappel non valide

Lors d'un développement d'une application web avec AJAX, j'ai rempli un contrôle ListBox avec AJAX.
Il y a plusieurs appels AJAX sur la page avant Postback (que l’on clique sur le bouton pour enregistrement).
Au moment de l’enregistrement, j'ai eu le message d'erreur suivant:
Argument de publication ou de rappel non valide. La validation d'événement est activée via <pages enableEventValidation="true"/> dans la configuration ou via <%@ Page EnableEventValidation="true" %> dans une page. Pour des raisons de sécurité, cette fonctionnalité vérifie si les arguments des événements de publication ou de rappel proviennent du contrôle serveur qui les a rendus à l'origine. Si les données sont valides et attendues, utilisez la méthode ClientScriptManager.RegisterForEventValidation afin d'inscrire les données de publication ou de rappel pour la validation.
Depuis  ASP.NET 2.0 il y a une validation d'événement qui vérifie la requête POST pour s'assurer  que l'événement de Postback est valide.
La validation d'événement évite l'attaque qui renvoi  des données par l'événement qui ne vient pas d’un contrôle enregistré sur la page
Les solutions qui peuvent corriger le problème :
1. Désactiver la validation d'événement (mais risque de sécurité) avec EnableEventValidation = false.
2. Utiliser la méthode d’Ajax UpdatePanel (Placer le contrôle Listbox dans UpdatePanel et déclencher la mise à jour  pour ajouter/supprimer des éléments de Listbox). Le viewstate sera alors mis à jour et la validation d'événement passe.
3. Utiliser Postback pour ajouter/supprimer les items du contrôle ListBox
J'ai réussi à ne plus avoir ce message d'erreur en vidant le contenu du contrôle  ListBox  lors de l’envoi de formulaire. Le contrôle ListBox reste en même état avant et après Postback.
document.forms[0].onsubmit = function() {
    document.getElementById('IdControle').options.length = 0;
};

jeudi 7 octobre 2010

Ajax sur Firefox : La fonction onreadystatechange n'est pas appelée

Sur un projet dans lequel on a utilisé l’Ajax, on a rencontré un problème de code qui fonctionne bien sur Internet Explorer mais pas sur Firefox. Le problème réside dans la fonction définie par l’attribut "onreadystatechange" qui n'est jamais appelée sur Firefox.

Ci-dessous le déroulement d'appel Ajax que l'on a fait initialement:
-Instancier la classe XMLHttpRequest selon le navigateur.
-Définir la fonction qui doit être appelée dans l’attribut «onreadystatechange » (événement pour le changement d'état).
-Ouvrir la connexion avec la méthode « open »
-Envoyer la requête sur serveur de manière synchrone.
-Vérifier si l’état est prête/les données sont disponibles ou reçues (l'état défini par l'attribut « readyState » est égale à 4)
-Vérifier si le statut est égal à 200 = OK, 404 si non trouvé.
-Exécuter la fonction définie par « onreadystatechange »

var req;
function Initialize()
{
                try
                {
// Version récente d’Internet Explorer
                               req=new ActiveXObject("Msxml2.XMLHTTP");
                  }
                catch(e1)
 {
                               try
                                 {
// Version plus ancienne d’Internet Explorer
                                               req=new ActiveXObject("Microsoft.XMLHTTP");    }
                               catch(e2)
                               {
                                               req=null;
                               }
}
if(!req && typeof(XMLHttpRequest)!="undefined")
{
                               req=new XMLHttpRequest(); // Firefox, Safari,..
}
}

function Fonction1()
{
                Initialize();
if(req!=null)
{
                   /* 0= non initialisé,
                  si la connexion est déjà initialisée, on annule la requête HHTP
                  */
                  if (req.readyState != 0)
                        req.abort();
        
                 req.onreadystatechange = Fonction2;
                 /* mode : POST/GET,
    url : url où trouver les données,
    type : true(asynchrone), false(synchrone)
 */
                 req.open(mode,url, type);
                 req.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
                var data = "AJAX_QUERY= TestAjaxQuery" ;
                req.send(data);
}
}

function Fonction2()
{
                if (req.readyState == 4)
                {
                                if (req.status == 200) // OK
                                {
                                               Alert('Retour Ajax OK');
                               }
                }
}

La fonction « Fonction2 » n’est jamais exécutée sur Firefox. Après les recherches, j’ai découvert que cette anomalie est reproductible pour l’appel synchrone sur Firefox et il s'agit d'un bug.
https://bugzilla.mozilla.org/show_bug.cgi?id=412112
Sur un appel synchrone, Firefox considère que la réponse d'Ajax est déjà arrivée et qu'il n'est pas nécessaire de vérifier l'état. Par conséquence, il n’est pas nécessaire de vérifier le changement d’état.

Le code devient alors comme suit:


function Fonction1()
{
         Initialize();
        if(req!=null)
        {
           /*0= non initialisé
            si la connexion est déjà initialisée, on annule la requête HHTP
          */
              if (req.readyState != 0)
                   req.abort();

             /* mode : POST/GET,
 url : url où trouver les données,
type : true(asynchrone), false(synchrone)
*/
             req.open(mode,url, type);
             req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
             var data = "AJAX_QUERY= TestAjaxQuery";
             req.send(data);
            //req.onreadystatechange = Fonction2
           if (req.status == 200) // OK
           {
            Fonction2();
           }
       }
}

function Fonction2()
{
     Alert('Retour Ajax OK');
}

Cette fois ci le code fonctionne sur Internet Explorer et sur Firefox.

Pour voir plus loin:
http://www.onejohn.org/wpjohn/2008/05/firefox-bug-with-onreadystatechange/
http://lukav.com/wordpress/2007/04/12/firefox-firebug-and-synchronos-calls-problem/



jeudi 2 septembre 2010

Google Map à partir de XML

Lors d’un projet j’ai eu l’occasion d’intégrer Google Map dans une application web.Le but de visualiser la dispersion des élèves d’une école dans le monde.

Les étapes à faire :
1. Interroger le service Geocoder de Google afin d’obtenir les coordonnées de chaque pays.Je crée une classe nommée Geocodeur avec une méthode static et publique qui permet d’obtenir latitude et longitude d’un pays.
using System.Net;
public class Geocodeur
{
         private static string _googleUri = “http://maps.google.com/maps/geo?q=”;
         // [CLE] est une clé propre à un domain obtenu en s’inscrivant sur Google 

         //http://code.google.com/intl/fr/apis/maps/signup.html
        private static string _googlekey = [CLE];
       private const string _output = "csv";      


      // Constituer l'url pour intérroger le service geocodeur de Google
      private static string getGeocodeUri(string adresse)
 {
           adresse = HttpUtility.UrlEncode(adresse);
           return string.Format("{0}{1}&output={2}&key={3}",_googleUri,adresse,_output,_googlekey);
    }
   // Intérroger le service de geocodeur de Google
    public static string getCoordonne(string adresse)
{
       WebClient client = new WebClient();
      string uri = getGeocodeUri(adresse);
     byte[] datageo= client.DownloadData(uri);
     return System.Text.Encoding.ASCII.GetString(datageo);
    }

    public Geocodeur()
 {
     }
}

String strCoordonne = Geocodeur.getCoordonne(“France”);
String strLat = strCoordonne.Split(',')[2];
String strLng = strCoordonne.Split(',')[3];

2. Enregistrer ensuite ces coordonnées avec les informations à afficher pour chaque pays dans un fichier xml.
Le fichier possède la structure ci-dessous:

 
3. Créer une page html qui héberge le Google map. 4. Dans le fichier javascript Geo.js, je charge le fichier xml créé précédemment :
// Pour savoir si navigateur est ie6

var is_ie6 = ( window.external && typeof window.XMLHttpRequest == "undefined");
var iconeGrande = new GIcon();
iconeGrande.image = 'iconeGrande.png';

// Résoudre le problème IE6 qui ne supporte pas l'image png avec transparent
if(is_ie6){ iconeGrande.image = 'iconeGrande.gif';}
iconeGrande.shadow = null;i
coneGrande.iconSize = new GSize(48, 48);
iconeGrande.shadowSize = null;
iconeGrande.iconAnchor = new GPoint(24,24);
iconeGrande.infoWindowAnchor = new GPoint(24,24);
var iconePetite = new GIcon();

iconePetite.image = 'iconePetite.png';

if(is_ie6){ iconePetite.image = 'iconePetite.gif';}
iconePetite.shadow = null;
iconePetite.iconSize = new GSize(32, 32);
iconePetite.shadowSize = null;
iconePetite.iconAnchor = new GPoint(16,16);
iconePetite.infoWindowAnchor = new GPoint(16,16);
var map;

var xml;

function initialiserGoogleMap(langue)
{
       map = new GMap2(document.getElementById("map_canvas"));
      map.setCenter(new GLatLng(33.8869170,9.5374990), 2, G_NORMAL_MAP);
      map.addControl(new GLargeMapControl());
      GDownloadUrl("geolocalisation.xml", function(data,responseCode)
{
        if(responseCode == 200) { xml = GXml.parse(data);
        var markers = xml.documentElement.getElementsByTagName("marqueur");
       for (var i = 0; i < markers.length; i++)
       {
            var icone;
            var id = markers[i].getAttribute("id");
           // Si la France, on utilise grande icone
          if(id == '1') { icone = iconeGrande; }
         else { icone = iconePetite; }

         var information;
         if(langue == "fr")  {
              information = markers[i].getAttribute("informationFr");
         }
        else  {
              information = markers[i].getAttribute("informationEn");
         }

        var nombre = markers[i].getAttribute("nombre");
        var point = new GLatLng(parseFloat(markers[i].getAttribute("latitude"))
                      , parseFloat(markers[i].getAttribute  ("longitude")));
         var marker = createMarker(point, nombre,information, icone);
         map.addOverlay(marker);
    }
}
else {

  alert ('Erreur');
}
});
}

function createMarker(point, nombre, information,icone){
         var marker = new GMarker(point, icone); 

         var html = information;
GEvent.addListener(marker, 'click', function() { marker.openInfoWindowHtml(html, {beakOffset:03}); });
        return marker;
}

mardi 20 juillet 2010

Installer le Framework .NET sur un poste client via code (sans être administrateur)


Lors d'un projet de migration des applications Winforms du .NET 1.1 vers .NET 3.5, on a réfléchi au moyen de déploiement du Framework .NET3.5 sur les postes d'utilisateurs. Les utilisateurs ne sont pas administrateurs sur les postes. Ils ne peuvent donc pas installer le Framework eux-mêmes. Le déploiement de l'application se fait par ClickOnce via un URL :
http:// [url]. [Domaine].com/[nomapplication].application
Une possibilité était d'inclure le code d'installation de Framework .NET3.5 lors de lancement de l'application.

Les principes :
1. Vérifier si le Framework .NET 3.5 est installé sur le poste client.
2. Si Oui, installer l'application.
3. Si Non, installer d'abord le Framework .NET 3 .5 en utilisant un profil administrateur, puis installer l'application.

Les étapes :
1. J'ai défini dans le fichier de configuration le chemin vers l'exécutable du Framework .NET3.5, le nom ainsi que le mot de passe d'utilisateur qui installera l'application et l'url de l'application.
……..
<appSettings>
<add key= "application" value="http://[URL application]\setup.exe"/>
<add key= "cmd" value="http://[URL application]\setup.exe"/>
<add key= "user" value="[Nom utilisateur]"/>
<add key= "pwd" value="[Mot de passé utilisateur]"/>
<add key= "domain" value="[Domaine utilisateur]"/>
</appSettings>
……
2. J'ai créé une classe qui contient une méthode permettant de lancer un processus sous le contexte d'un utilisateur. J'ai eu le code ci-dessous en cherchant sur internet.

Pour voir plus loin : http://msdn.microsoft.com/en-us/library/ms682431(VS.85).aspx

using System;using Microsoft.Win32;
using System.Runtime.InteropServices;
using System.Diagnostics;
namespace Lanceur
{
        /// 
       /// Description résumée de CreateProcessWithLogon.
       /// 
      public class CreateProcessWithLogon
     {
               public const UInt32 Infinite = 0xffffffff;
              public const Int32 Startf_UseStdHandles = 0x00000100;
              public const Int32 StdOutputHandle = -11;
             public const Int32 StdErrorHandle = -12;
           [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
           public struct StartupInfo
  {
                public int cb;
               public String reserved;
               public String desktop;
              public String title;
              public int x;
              public int y;
              public int xSize;
              public int ySize;
              public int xCountChars;
              public int yCountChars;
              public int fillAttribute;
              public int flags;
              public UInt16 showWindow;
              public UInt16 reserved2;
              public byte reserved3;
              public IntPtr stdInput;
              public IntPtr stdOutput;
              public IntPtr stdError;

public struct ProcessInformation
{
                public IntPtr process;
               public IntPtr thread;
               public int processId;
               public int threadId;
}
[DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
public static extern bool CreateProcessWithLogonW(
String userName,
String domain,
String password,
UInt32 logonFlags,
String applicationName,
String commandLine,
UInt32 creationFlags,
UInt32 environment,
String currentDirectory,
ref StartupInfo startupInfo,
out ProcessInformation processInformation);
[DllImport("kernel32.dll", SetLastError=true)]
public static extern bool GetExitCodeProcess(IntPtr process, ref UInt32 exitCode);
[DllImport("Kernel32.dll", SetLastError=true)]
public static extern UInt32 WaitForSingleObject(IntPtr handle, UInt32 milliseconds);
[DllImport("Kernel32.dll", SetLastError=true)]
public static extern IntPtr GetStdHandle(IntPtr handle);
[DllImport("Kernel32.dll", SetLastError=true)]
public static extern bool CloseHandle(IntPtr handle);
public static void Launch(string command, string user, string domain, string pwd){
StartupInfo startupInfo = new StartupInfo();
startupInfo.reserved = null;
startupInfo.flags &amp;= Startf_UseStdHandles;
startupInfo.stdOutput = (IntPtr)StdOutputHandle;
startupInfo.stdError = (IntPtr)StdErrorHandle;
UInt32 exitCode = 123456;
ProcessInformation processInfo = new ProcessInformation();
String currentDirectory = System.IO.Directory.GetCurrentDirectory();
try
{
CreateProcessWithLogonW(
user,
domain,
pwd,
(UInt32) 1,
command,
command,
(UInt32) 0,
(UInt32) 0,
currentDirectory,
ref startupInfo,
out processInfo);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
Console.WriteLine("Exécution ...");
WaitForSingleObject(processInfo.process, Infinite);
GetExitCodeProcess(processInfo.process, ref exitCode);
Console.WriteLine("Code sortie: {0}", exitCode);
CloseHandle(processInfo.process);
CloseHandle(processInfo.thread);
}
}
}

 3. Dans le point d'entrée de l'application, j'ai ajouté le code pour vérifier l'existence du Framework .NET 3.5 sur le poste client en vérifiant la présence de clé dans la base de registre.
using System.Windows.Forms;
using System.Data;
using System.Configuration;
using System.Collections;
using Microsoft.Win32;
namespace Lanceur
{
///
/// Classe lanceur
///
public class MainClass
{
///
/// Point d'entrée principal de l'application.
///[STAThread]
static void Main()
{
string application = ConfigurationSettings.AppSettings["application"];
try
{
Console.WriteLine("Vérification de composant requis.");
// Vérifier si framework .NET3.5 est installé
if(!NETInstalled())
{
Console.WriteLine("Installation de composant requis en cours ....");
string cmd = @ConfigurationSettings.AppSettings["cmd"];

// Installation en silence
cmd = cmd + " /q:a /c:'setup.exe /q /norestart' /norestart";
string user = ConfigurationSettings.AppSettings["user"];
string domain = ConfigurationSettings.AppSettings["domain"];
string pwd = ConfigurationSettings.AppSettings["pwd"]; 

// Installation du framework .NET 3.5
CreateProcessWithLogon.Launch(cmd, user,domain,pwd);
Console.WriteLine("Installation de composant requis est teminé.");

// Installation de l'application
Console.WriteLine("Installation de l'application.");
InstallApplication(application);
}
else
{
// Installation de l'application
Console.WriteLine("Installation de l'application.");
InstallApplication(application);
}
}
catch(Exception Ex)
{…}

// Lancement de l'application
try
{
// Code propre à l'application
Application.Run((System.Windows.Forms.Form)(IHMManager.Instance.getIhmPrincipale()));
}
catch(….)
{}
}
}
private static bool NETInstalled()
{
RegistryKey cle = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\NET Framework Setup\\NDP\\v3.5");
         if(cle == null)
        {
return false;
        }
       return true;
}

private static void InstallApplication(string application)
{
    try
  {
       // Prévenir l'utilisateur qi tout se passe bien, de passer par la nouvelle icône pour la prochaine connexion        MessageBox.Show("L'application [NOMAPPLICATION] sera installée sur votre poste. Merci de cliquer sur  la nouvelle icône sur votre bureau pour la prochaine connexion.");
System.Threading.Thread.Sleep(500);
System.Diagnostics.Process.Start(@application);
System.Diagnostics.Process.Start(@application);
 }
catch(Exception Ex)
{
throw Ex;
}
}
}
}

Cette solution n'a finalement pas été retenue. Mais cela me permet de découvrir une possibilité sur le moyen d'installation les éléments requis. 

On utilise finalement un outil PsTools->PsExec qui permet d'installer à distance et en masse une application sur un poste client sous contexte d'un administrateur.

jeudi 8 juillet 2010

Astuce pour afficher les résultats de requête sur une seule ligne/sous forme d’une chaine de caractères

Une fois je voulais récupérer les données d’une table de la base de données SQL Server 2008 sous forme d’une chaîne de caractères. Je pensais faire une fonction avec un curseur et concatène des données de chaque ligne pour former une chaîne de caractères.

Supposons, il y a une table suivante dans une base de données :
Table dbo.PAYS
ID LIBELLE
1 France
2 Allemagne
3 Belgique
4 Espagne

Lorsque l’on fait la requête: SELECT LIBELLE FROM dbo.PAYS, le résultat affiché:
LIBELLE
France
Allemagne
Belgique
Espagne

Une astuce facile pour former une chaîne de caractère à partir de ce résultat est d’utiliser le mot clé : FOR XML PATH

Le mode FOR XML PATH est utilisé pour construire un XML à partir de résultats de requête.

Examples:
Requête
SELECT dbo.PAYS.LIBELLE
FROM dbo.PAYS
ORDER BY dbo.PAYS.ID
For XML PATH
Résultat
<row>
<LIBELLE>France</LIBELLE>
</row>
<row>
<LIBELLE>Allemagne</LIBELLE>
</row>
<row>
<LIBELLE>Belgique</LIBELLE>
<row>
<row>
<LIBELLE>Espagne</LIBELLE>
</row>

Requête:
SELECT dbo.PAYS.LIBELLE + ''
FROM dbo.PAYS
ORDER BY dbo.PAYS.ID
For XML PATH ('Pays')
Résultat :
<Pays>France</Pays>
<Pays>Allemagne</Pays>
<Pays>Belgique</Pays>
<Pays>Espagne</Pays>

Et, la requête suivante:
SELECT dbo.PAYS.LIBELLE + ','
FROM dbo.PAYS
ORDER BY dbo.PAYS.ID
For XML PATH ('')
Et voila le résultat:
France,Allemagne,Belgique,Espagne

Voir plus loin sur FOR XML PATH :
http://technet.microsoft.com/fr-fr/library/ms189885(SQL.90).aspx

mercredi 7 juillet 2010

Problème de génération Word via une tâche planifiée

J’ai travaillé sur un projet .NET C# qui génère des documents Word via un exécutable lancé par une tâche planifiée. J'utilise Interop.Word pour la génération des documents.

L'exécutable a été installé sur Windows Server 2003, la génération se passait bien.
Récemment, on a migré l'application sur Windows Server 2008. Depuis la génération ne marchait plus.

Apres plusieurs recherches, j'ai pu trouver le "hack" pour contourner ce problème:

1. Sur Windows x86
-Créer le répertoire c:\\windows\system32\config\systemprofile\desktop
-Accorder les droits au compte qui exécute la génération Word

2. Sur Windows x64
-Créer le répertoire c:\\windows\SysWOW64\config\systemprofile\desktop
-Ajouter les droits au compte qui exécute la génération Word

Le répertoire Desktop est apparemment nécessaire pour l’ouverture du fichier Office. Ce répertoire existe sur Windows Server 2003 mais disparaît sur Windows Server 2008.

Pour suivre la discussion sur ce sujet:
http://social.msdn.microsoft.com/Forums/en/innovateonoffice/thread/b81a3c4e-62db-488b-af06-44421818ef91