mardi 14 juin 2011

Premiers pas en HTML 5 – Canvas

L’heure est venu de s’intéresser progressivement à HTML 5, dans la mesure où la spécification semble pouvoir prochainement dépasser le stade du Working Draft.

Donc, par où commencer? HTML 5 propose tout un tas de fonctionnalités hétérogènes. Certaines sont déjà supportées par la plupart des navigateurs, d’autres pas. Elles ne sont d’ailleurs visiblement pas toutes au même niveau d’avancement. Certaines portent sur l’aspect graphique, d’autres sur la communication ou encore sur la validation des formulaires par exemple. De même, les tutoriaux disponibles sont nombreux, mais dispersés. Donc pour démarrer, la façon la plus logique de procéder est de se concentrer sur les parties relativement stables et déjà implémentées par les navigateurs courants :

  • Les fonctionnalités multimédia (audio, vidéo, dessin, etc.)
  • Les fonctions de drag&drop
  • Le storage

Plus précisément, l’idée est ici de s’intéresser en premier lieu au Canvas, une nouvelle balise introduite par HTML 5 qui permet de définir une région dans laquelle on peut dessiner. Après avoir fastidieusement récupéré des bouts de code pour constituer un exemple simple et fonctionnel quelque soit le navigateur client, voici un premier résultat :

<!DOCTYPE html>
<html>
<head>
<title>Bouncing balls demo</title>
</head>
<body>
<section id="wrapper">
<header>
<h1>Bouncing balls</h1>
</header>
<article></article>
</section>
<script>
// global variables
var vSpeedInc = 1;
var hSpeed = 5.0;
var vSpeed = 1.0;
var circleRadius = 20;
var circleXPos = 100.0;
var circleYPos = 100.0;
var width = 800;
var height = 600;
var framerate = 50;

// start animation at startup
startAnimation();

function startAnimation(data) {
// canvas initialization
var canvas = document.createElement('canvas');
canvas.height = height;
canvas.width = width;
document.getElementsByTagName('article')[0].appendChild(canvas);
var ctx = canvas.getContext("2d");

// lauch timer
window.canvasTimer = setInterval(draw, 1000 / framerate);

function draw() {
// draw
ctx.clearRect(0, 0, width, height);
ctx.strokeStyle = "#000000";
ctx.fillStyle = "#FF0000";
ctx.beginPath();
ctx.arc(circleXPos, circleYPos, circleRadius, 0, Math.PI * 2, true);
ctx.closePath();
ctx.stroke();
ctx.fill();

// compute
if (circleXPos + circleRadius >= width || circleXPos - circleRadius <= 0)
hSpeed = -hSpeed;


if (circleYPos + circleRadius + vSpeed >= height) {
vSpeed = -vSpeed;
}
else {
vSpeed = vSpeed + vSpeedInc;
}

circleXPos = circleXPos + hSpeed;
circleYPos = circleYPos + vSpeed;
}
}
</script>
</body>
</html>



Ce code anime une balle qui rebondit et se cogne sur les coins du Canvas. Il fonctionne seul, par simple copier/coller dans une page HTML vierge. Il représente donc un bon point de départ pour des expérimentations diverses, ce qui manque visiblement dans les différents tutoriaux disponibles.

vendredi 3 juin 2011

DbConnectionStringBuilder

Il y a plus de 10000 types définis dans le Framework .NET. Il est donc logique que quelques uns d’entre eux soient à la fois très utiles et méconnus. DbConnectionStringBuilder (et ses classes spécialisées comme SqlConnectionStringBuilder pour SQL Server) est sans doute l’un d’entre eux. Il permet de manipuler des chaines de connexion pour les providers communs par code (propriétés et accesseurs) sans avoir à traiter directement la chaine de caractères. Le code est alors plus lisible et il y a moins de risque d’erreur.

Dans la plupart des cas, les chaines de connexions définies habituellement dans le fichier de configuration n’ont pas besoin d’être modifiées à la volée. Néanmoins, certaines situations peuvent justifier l’utilisation de ces classes :

  • L’application accède à de multiples bases de données et il n’est pas possible de déterminer à la compilation quelle(s) base(s) de données sont sollicitées.
  • L’application accède à une base de données pour laquelle les informations nécessaires à la connexion ne sont pas connues lors de la compilation, par exemple parce qu’elles sont fournies par l’utilisateur.
  • L’application utilise des logins/passwords différents pour la connexion à la base de données en fonction des droits accordés à l’utilisateur connecté.
  • Ecriture de code pour l’industrialisation d’un logiciel automatisant la configuration en fonction des plateformes développement/intégration/préproduction/production.

En l’occurrence, l’utilisation de SqlConnectionStringBuilder s’est imposée pour basculer dynamiquement entre SQL Server et SQL Azure, à l’aide d’un code de ce type :

private string connectionString = null;

private string userIdAzure = "*****";
private string serverAzure = "*****";
bool azureContext = true;

public DataAccess()
{
connectionString = ConfigurationManager.ConnectionStrings["Connexion"].ConnectionString;
if (azureContext)
{
SqlConnectionStringBuilder scsb = new SqlConnectionStringBuilder(connectionString);
scsb.DataSource = serverAzure;
scsb.UserID = userIdAzure;
connectionString = scsb.ConnectionString;
}
}



On voit que le champ azureContext permet de basculer dynamiquement entre SQL Server et SQL Azure, ce qui est parfaitement envisageable tant que les limitations inhérentes à SQL Azure sont respectées.


Note : Dans mon cas, le mot de passe et le nom de la base de données sont identiques pour la base SQL Server et la base SQL Azure ; seuls l’User Id et le nom du serveur ont eu besoin d’être modifiés.

mardi 31 mai 2011

Microsoft Days 2011 à Strasbourg

Les Microsoft Days 2011 de Strasbourg auront lieu le 8 Juin prochain. Vous pouvez vous inscrire .

Pour ma part, j’y animerai 2 sessions pendant la matinée :

  • “Du Framework .NET 2.0 au Framework .NET 4.0 - Tout ce que vous avez manqué” avec Stéphanie Hertrich de Microsoft.
  • “Windows Azure - Approche pragmatique d'une révolution en marche”, avec mon collègue Philippe Geiger.

Je donne rendez-vous avec grand plaisir à tout ceux qui souhaitent participer à cet évènement.

Fonction d’exportation simple

Beaucoup d’applications proposent des fonctionnalités d’exportation de grilles de données vers des formats tels que CSV ou Excel. L’objectif de cet article est de proposer la fonction la plus simple et la plus générique possible pour répondre à cette problématique courante.

Ainsi, pour rendre la fonction la plus générique possible :

  • La réflexion est utilisée pour détecter dynamiquement les propriétés
  • Des paramètres optionnels (avec des valeurs par défaut) permettent d’utiliser la même fonction en faisant varier les détails d’implémentation
  • La fonction est une méthode d’extension sur IEnumerable<T>, ce qui la rend applicable dans quasiment tous les cas d’utilisation.

La fonction

Voici la fonction principale d’export :

public static class ExportationExtensions
{
public static string Export<TSource>(this IEnumerable<TSource> items,
bool includeTitleRow = true,
string header = "", string footer = "",
string rowPrefix = "", string rowSuffix = "",
string rowSeparator = "\r\n", string itemSeparator = ",",
Type ignoreFieldHavingAttributeType = null)
{
// serializable properties
var serializableProperties = typeof(TSource).GetProperties().AsEnumerable();
if (ignoreFieldHavingAttributeType != null)
serializableProperties = serializableProperties
.Where(pi => pi.GetCustomAttributes(false)
.All(o => o.GetType().FullName != ignoreFieldHavingAttributeType.FullName));

// result
return items.Aggregate(
string.Concat(header, serializableProperties
.Aggregate(rowPrefix,
(str, sp) => string.Concat(str, sp.Name, itemSeparator),
res => string.Concat(res, rowSuffix, rowSeparator))),
(row, item) => string.Concat(row, serializableProperties
.Aggregate(rowPrefix,
(str, sp) => string.Concat(str, sp.GetValue(item, null).ToString(), itemSeparator),
res => string.Concat(res, rowSuffix)), rowSeparator),
rows => string.Concat(rows, footer));
}
}



L’exportation produit par défaut des données au format CSV.


Utilisation


La fonction prévoit l’écriture éventuel de la ligne de titre, le préfixe et le suffixe de la chaine produite et de chaque ligne, ainsi que le séparateur entre chaque élément. En outre, afin de ne pas sélectionner certaines propriétés à exporter, la fonction élimine les propriétés sur lesquelles sont appliquées un attribut particulier (de la même manière que l’attribut XmlIgnore empêche la sérialisation XML du champ sur lequel l’attribut porte). Voici un exemple d’export d’une liste au format CSV puis sous forme de tableau HTML (ce qui permet l’ouverture directe par Excel par exemple) :

class Person
{
[DebuggerDisplay("")]
public string Nom { get; set; }
public string Prenom { get; set; }
public int Age { get; set; }
}

class Program
{
static void Main(string[] args)
{
List<Person> personnes = new List<Person>() {
new Person(){ Age = 55, Nom = "Gates", Prenom = "Bill"},
new Person(){ Age = 48, Nom = "Obama", Prenom = "Barack"},
new Person(){ Age = 26, Nom = "Damaj", Prenom = "Dany"}
};

Console.WriteLine(personnes.Export());

Console.WriteLine();

Console.WriteLine(personnes.Export(header: "<table>", footer: "</table>",
rowPrefix: "<tr><td>", rowSuffix: "</td></tr>",
itemSeparator: "</td><td>",
ignoreFieldHavingAttributeType: typeof(DebuggerDisplayAttribute)));

Console.ReadLine();
}
}



Champ d’application et perspectives


La fonction présentée ci-dessus a été écrite à l’occasion de la recherche d’une fonction d’Export CSV pour les tables Azure. Comme la plupart des fonctions très (trop?) génériques, ses performances ne sont pas excellentes en raison de l’utilisation de la réflexion et du type String en retour de la méthode (un fonctionnement par buffer serait préférable). D’autre part, le choix d’un séparateur entre éléments et d’un préfixe/suffixe pour chaque entité est celui qui semble être le plus logique, mais il ne convient sans doute pas à toutes les implémentations.

jeudi 19 mai 2011

Utilisation de MsBuild pour automatiser les déploiements

Le déploiement est une étape critique de la vie d’un projet. Cependant, la plupart du temps, il n’existe aucune procédure automatisée, et parfois même aucune documentation. Il existe pourtant de nombreux outils permettant d’automatiser la tâche de déploiement, parmi lesquels figurent NAnt et MSBuild par exemple.

Qu’est-ce que MSBuild?

MSBuild est le système de Build de Visual Studio, ce qui veut notamment dire que tous les fichiers projet (.csproj par exemple) utilisent ce système. Par exemple, si on crée une application console par Visual Studio et qu’on ouvre directement le fichier .csproj avec un éditeur de texte, on obtient :

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>04c1eb93-daac-4486-8781-7c0e8dd5967c</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ConsoleApplication1</RootNamespace>
<AssemblyName>ConsoleApplication1</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<TargetFrameworkProfile>Client</TargetFrameworkProfile>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<PlatformTarget>x86</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<PlatformTarget>x86</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>



On voit que MSBuild est en fait basé sur XML, et que les fichiers MSBuild respectent un schéma bien précis. Ce dernier permet de définir notamment :



  • Des Target identifiés par leur nom, permettant de regrouper des tâches et d’organiser leur exécution.

  • Des Task représentant une action unitaire pour le système MSBuild. La liste des tâches disponible permet de donner rapidement une vue d’ensemble de ce qu’on peut faire avec MSBuild (on y retrouve les tâches associées à la compilation notamment). Le système prévoit une extensibilité par l’implémentation d’une interface ITask et par l’utilisation de la balise Import.

  • Des ItemGroup contenant des Item représentant des fichiers, la plupart du temps en vue d’une compilation.

  • Des PropertyGroup représentant de définir des propriétés personnalisées.

Un véritable mini-langage pour le déploiement


MSBuild ne s’arrête pas là. A l’aide des éléments When, Choose et Otherwise et des attributs Condition et DependsOn (pour les Target), on peut très rapidement développer une procédure de déploiement complexe qui répond aux problématiques courantes :



  • Création automatique de backups estampillés à la date du jour.

  • Notification automatique de l’équipe projet lorsqu’un nouveau déploiement a lieu.

  • Prise en compte des différences d’environnement entre déploiements en intégration/préproduction/production (notamment pour prendre les bons fichiers de configuration et les bons identifiants pour les serveurs de destination).

  • Lancement automatique de tests unitaires avant tout déploiement.

  • Tout autre traitement automatisé à l’aide de la tâche Exec.

En outre, de nombreuses tâches récurrentes (envoi de mails, compression, archivage sur le contrôleur de code source, copie sur serveur FTP, etc.) sont déjà développées dans le MSBuild Community Tasks Project et viennent largement consolider les tâches MSBuild de base.


On dispose finalement d’un véritable mini-langage compréhensible et extensible pour la réalisation de tâches de déploiement. Pour exécuter le fichier MSBuild et lancer un déploiement, il suffit d’utiliser l’outil msbuild du Framework .NET. Par exemple, en créant un .bat avec le code suivant, où BuildAll.xml est le fichier MSBuild et “Package” un Target à l’intérieur du fichier MSBuild qu’on souhaite exécuter explicitement :

call "%VS100COMNTOOLS%vsvars32.bat"
msbuild BuildAll.xml /t:Package



Un exemple concret


Pour finir, voici un exemple concret d’utilisation d’MSBuild, où la cible Package permet de :



  • Compiler un projet en utilisant comme répertoire de sortie Build\

  • D’exécuter des tests unitaires

  • De créer un fichier Release.txt contenant la date et l’heure courante dans le répertoire Build\

  • De compresser le répertoire Build\

  • De supprimer le répertoire temporaire Build\
<Project DefaultTargets="Package" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
<PropertyGroup>
<MSBuildCommunityTasksPath>chemin de la dll…</MSBuildCommunityTasksPath>
</PropertyGroup>
<Import Project="Dependencies\MSBuild.Community.Tasks.Targets"/>

<PropertyGroup>
<SourceDir>répertoire contenant les sources…</SourceDir>
<BuildDir>Build\</BuildDir>
</PropertyGroup>

<Target Name="Build">
<RemoveDir Directories="$(BuildDir)" ContinueOnError="true" />
<MakeDir Directories="$(BuildDir)" ContinueOnError="false" />

<MSBuild Projects="$(SourceDir)\ExempleDeDossier\ExempleDeProjet.csproj"
Properties="Configuration=Release;DebugSymbols=false;DebugType=none;OutDir=$(MSBuildProjectDirectory)\$(BuildDir)"
BuildInParallel="True">
</MSBuild>

<Prompt Text="Build Terminated. Hit [ENTER] ..." />
</Target>


<Target Name="Test" DependsOnTargets="Build">
<Exec Command="MsTest /noresults /testmetadata:&quot;$(SourceDir)\ExempleDeFichierDeTests.vsmdi&quot;" />

<Prompt Text="Tests Terminated. Hit [ENTER] ..." />
</Target>

<Target Name="Package" DependsOnTargets="Build;Test">
<Time Format="yyyyMMddHHmmss">
<Output TaskParameter="FormattedTime" PropertyName="CurrentDate" />
</Time>

<WriteLinesToFile File="$(BuildDir)\Release.txt" Lines="$(CurrentDate)" Overwrite="true" />

<ItemGroup>
<ZipFiles Include="$(BuildDir)\**\*.*" />
</ItemGroup>

<Zip Files="@(ZipFiles)"
ZipFileName="$(MSBuildProjectDirectory)\LeFichierDeBackup.zip" ZipLevel="9" />

<RemoveDir Directories="$(BuildDir)" ContinueOnError="false" />

<Prompt Text="Package Terminated. Hit [ENTER] ..." />
</Target>
</Project>