29 octobre 2008

C# 4 et Visual Studio 2010 CTP en téléchargement

A l'adresse suivante, vous trouverez une image virtuelle (Virtual PC 2007 SP1) où est installé Visual Studio 2010 !

La VM est découpée en plusieurs archives, d'un total de 8 Go. Une fois installée, l'image nécessite 75 Go d'espace libre, outch ! Mais bon, une fois passé ces petites contraintes, vous aurez l'immense privilège d'essayer C# 4 avec sa DLR, son nouveau mot clé dynamic et la covariance et controvariance sur les interfaces (mot clés in et out), et bien d'autres surprises.

Va falloir que je fasse de la place ...

Bon téléchargement.

28 octobre 2008

Contenu de la PDC 2008

La PDC 2008 (Professional Developers Conference) s'est déroulée du 25 au 28 octobre. Durant ces 4 jours intensifs, ont été présentés les produits et technologies suivantes :

C# 4.0 : La version 1.0 introduisait le code managé, la 2.0 les generiques, la 3.0 LinQ, la version 4.0 introduit la programmation dynamique. Le type des objets peut être connu juste à l'exécution, c'est à dire comme dans un langage de script (php, javascript, python, etc ...). Ceci est possible grâce à la DLR (Dynamic Language Runtime). Cela fait déjà plus d'un an que l'on en parle, et le plus célèbre des projets est l'implémentation du langage python, ironpython, dans la plateforme .Net. A l'instar de la CLR qui unifiait tous les langages de programmation statiques, la DLR unifie tous les langages dynamiques. Libre à vous de développer votre propre langage dynamique. C# 4.0 dispose d'un nouveau mot clé : dynamic. En substituant le type de votre objet par dynamic, le type de votre objet n'est connu qu'à l'exécution. dynamic est particulièrement utile pour manipuler des objets créés par des scripts. Cela permet de mélanger programmation statique et dynamique au même endroit. Le créateur du C# fait d'ailleurs une démonstration bluffante en copiant/collant un morceau de Javascript dans du C#. Après avoir modifié quelques mots clés (function, var), le code compile et fonctionne. Il est aussi possible de définir des paramètres optionnels dans les méthodes, et de les spécifier en fournissant le nom du paramètre en question. Grâce au support des types dynamiques, des paramètres optionnels et nommés, l'utilisation des objets COM est grandement simplifiée. Anders Hejlsberg part d'un exemple d'automation Excel, et simplifie petit à petit tous les appels aux objets et aux méthodes servant à piloter Excel. La covariance et controvariance a été amélioré, cela fonctionne maintenant avec les génériques de manière statique (mot clé in et out). En dernier lieu, le compilateur C# a été revu sous la forme d'un service pouvant être appelé simplement à partir d'un programme quelconque. Anders montre ainsi une console compilant et exécutant chaque ligne entrée. Le résultat est le même qu'avec un langage de script, le temps de compilation en plus bien sûr. Pour plus de détails, voici le lien de la conférence.

Visual Studio 10 : Toute l'interface de l'application est basée sur WPF. L'extensibilité a été fortement améliorée puisqu'il est possible d'intégrer des addins en copiant simplement l'assembly en question dans le répertoire prévu pour. Celui est pris en compte au prochain démarrage de l'IDE. Le designer pour Silverlight a aussi pris un coup de jeune et cela n'est pas un mal. Une gestion plus fine des fichiers de configuration a été mise en place, c'est à dire un par type de build (Debug, Release). D'ailleurs, voici l'url de la vidéo conférence.

Workflow Foundation : Une de mes briques préférées du Framework 3.0 a subit un petit lifting. Tout d'abord, l'actuel designer qui a la particularité d'être horiblement lent a été refait, ceci devant régler ce petit problème de performance. Les performances des workflows à l'exécution ont été largement améliorées. A noter aussi, la persistance des workflows est maintenant totalement personnalisable.

WPF, ASP.Net MVC, Surface SDK, Live Mesh (Bureau virtuel), Windows 7, Windows Azure : Je vous invite à aller voir le blog de Louis-Guillaume Morand pour plus de détails sur ces technologies et produits.

21 octobre 2008

Workflow Foundation : Hello World

Pour ne pas déroger à la tradition du HelloWorld, nous allons créer pour ce premier exemple, un workflow qui écrit HelloWorld et votre prénom dans la console. Cet exemple très simple a le mérite de décrire les étapes fondamentales du développement et de l'exécution d'un workflow séquentiel. On va donc voir comment créer un workflow avec un template de Visual Studio 2008 prêt à l'emploi. Nous verrons ensuite comment il est hébergé et exécuté grâce au moteur de workflow.

Dans un premier temps, créez d'abord un nouveau projet dans une nouvelle solution. Choisissez le template "Sequential Workflow Console Application", nommez le projet "HelloWorld" et la solution "HelloWorldWorkflow". Le projet créé est de type Console comme laisse présager le titre du template.


Ce template crée un workflow s'appelant Workflow1 dans un fichier de type cs (j'ai oublié de dire que le langage utilisé est le c#). En cliquant sur le petit plus, vous voyez apparaître le fichier Workflow1.designer.cs qui est le fichier généré par le designer de Workflow. Comme dans d'autres éditeurs fournis par Visual Studio, la classe Workflow1 est partielle (mot clé partial avant le nom de la classe), ce qui permet de définir dans 2 fichiers séparés, voir plus, la classe en question. Cliquez droit sur Workflow1.cs, et en choisissant View Code, vous verrez l'autre partie de la classe définissant le workflow.

Le template a aussi créé un fichier Program.cs qui contient le code nécessaire pour instancier et exécuter le workflow généré.

Sur ce, revenez sur le designer, ouvrez la Toolbox (boite à outils) et faites glisser l'activité Code qui est de type CodeActivity. Déposez là dans le workflow. Vous devriez avoir le résultat suivant :


Voila, nous venons d'insérer notre 1ere activité dans un workflow. Cette activité a pour but d'exécuter la logique métier que vous devez définir par l'intermédiaire d'une méthode. D'ailleurs, vous avez surement du remarquer le point d'exclamation dans le rond rouge en haut à droite de l'activité. Il signifie que justement, nous n'avons pas encore défini la méthode à exécuter et qu'il est nécessaire de le faire. Pour cela, double cliquez sur l'activité. Cela a pour effet de créer une méthode nommée "codeActivity1_ExecuteCode" et d'afficher le code de la classe Workflow1.cs. Dans l'autre partie de la classe générée par le designer, cela a instancié un délégué qui prend en argument la méthode précédemment créée et ce délégué est ajouté à l'événement ExecuteCode de l'activité.

Définissons le code de la méthode codeActivity1_ExecuteCode. Ce code va écrire dans la console "Hello World " suivi d'un nom qui sera passé en paramètre au workflow. Ajoutez d'abord la propriété Nom de type string comme cela :



public string Nom
{
get;
set;
}

Cette façon d'écrire une propriété, qui est une nouveauté du Framework 3.5, permet de générer à la fois l'accesseur (get), le mutateur (set) et le champ de type string associé. C'est un raccourci d'écriture qui est fort utile lorsque vous n'avez pas besoin de définir un comportement spécial pour le mutateur ou l’accesseur. Rajoutons ensuite la ligne suivante dans la méthode codeActivity1_ExecuteCode :



private void codeActivity1_ExecuteCode(object sender, EventArgs e)
{
Console.WriteLine("Hello World " + Nom);
}

Le workflow HelloWorld est terminé. Pour information, voici le code du workflow en entier (Workflow1.cs) :



using System;
using System.Workflow.Activities;

namespace HelloWorld
{
public sealed partial class Workflow1: SequentialWorkflowActivity
{
public string Nom
{
get;
set;
}

public Workflow1()
{
InitializeComponent();
}

private void codeActivity1_ExecuteCode(object sender, EventArgs e)
{
Console.WriteLine("Hello World " + Nom);
}
}
}

Tout d'abord le code généré dans la méthode Main qui est le point d'entrée de notre application console, sert à instancier et exécuter le workflow précédemment créé. Pour cela, nous devons d'abord instancier le moteur de workflow représenté par la classe System.Workflow.Runtime.WorkflowRuntime. Cette classe ou plutôt l'objet instancié de cette classe, fournit l'environnement d'exécution pour tous les workflows de l'application. Une seule instance de cette classe est autorisée par domaine d'application, comprenez par application pour simplifier. Il est bien évidemment possible de définir plusieurs domaines d'application dans une même application, mais ce n'est pas le sujet ici. Voici le code d'initialisation du moteur :



using(WorkflowRuntime workflowRuntime = new WorkflowRuntime())
{
}

L'instanciation du moteur est faite dans un bloc using (instanciation) {Suite d'instruction} car le moteur dérive de l'interface IDisposable, qui oblige la classe qui en hérite de définir une méthode Dispose. Celle-ci s'occupe de terminer l'exécution des workflows en cours et de libérer la mémoire utilisée. A la fin du bloc using, cette méthode est donc appelée implicitement. Toute nouvelle instruction prend place entre les accolades du using.

Poursuivons notre description. La ligne suivante déclare un objet de type System.Threading.AutoResetEvent et l'instancie :



AutoResetEvent waitHandle = new AutoResetEvent(false);

Cet objet est utile pour synchroniser 2 threads d'exécution. Il permet de stopper l'exécution d'un thread jusqu'à ce qu'un évènement particulier soit détecté, évènement déclenché dans un autre thread. Le thread déclenchant l'évènement le fait bien évidement au moment opportun, c'est-à-dire lorsqu'il a terminé ce qu'il a à faire.

Mais pourquoi avons-nous besoin d'un tel objet pour exécuter notre workflow HelloWorld ? Et bien, c'est simple. Le comportement par défaut du moteur de workflow est de créer un thread par workflow exécuté. Ce comportement est bien sûr modifiable, mais pour l'heure, nous ferons avec. Si nous exécutons notre workflow sans utiliser un objet AutoResetEvent, alors notre application instancie effectivement le workflow, le lance, ce qui crée un thread dédié où le workflow est exécuté. Cependant juste après la ligne lançant l'exécution du workflow, le flux d'exécution continuera sans attendre la fin du workflow, sortira ensuite du bloc using, ce qui appelera la méthode Dispose implicitement, qui aura pour effet de détruire l'instance du moteur et du workflow par la même occasion. Nous n'aurons même pas la chance de voir afficher notre "HelloWorld" dans la console. Avec un objet AutoResetEvent, cela nous permet d'attendre tranquillement la fin du workflow.

Définissons 2 délégués anonymes que l'on affectera à l'évènement WorkflowCompleted et WorkflowTerminated du moteur de workflow (ici workflowRuntime). Le 1er évènement est déclenché lorsqu'un workflow exécuté par le moteur se termine avec succès. L'autre, WorkflowTerminate, est déclenché lorsqu'un workflow se termine à cause d'une exception. Voici les 2 délégués :



workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e)
{
waitHandle.Set();
};

workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
{
Console.WriteLine(e.Exception.Message);
waitHandle.Set();
};

Chacun d'eux exécute la méthode Set() de l'objet waitHandle. Cette instruction marche de pair avec la méthode WaitOne() du même objet. Comme expliqué plus haut, un workflow est exécuté dans son propre thread. Ainsi pour attendre la fin du workflow, nous utilisons la méthode WaitOne() qui attend que la méthode Set() soit exécutée dans un autre thread.

Les 2 délégués sont donc exécutés par le thread du workflow. Leur exécution permet de poursuivre l'éxecution de notre programme principal (bloquée jusqu'alors par WaitOne() de l'objet waitHandle).

Notez que le délégué associé à l'évènement WorkflowTerminated récupère l'erreur générée par le workflow et l'écrit dans la console.

A ce stade, il ne reste plus qu'à définir les arguments du workflow, c'est-à-dire le nom qui s'affichera à côté de "HelloWorld", de lancer le workflow et finalement d'attendre la fin de son exécution. Pour définir les arguments d'un workflow, WF permet d'utiliser une Hashtable ou Dictionnaire. Cette structure de données associe une valeur clé (ici de type string) avec un objet et cette association de données définie un élément du dictionnaire. Voici le code pour définir le paramètre de notre workflow :


Dictionary<string,object> arguments = new Dictionary<string,object>();
arguments.Add("Nom", "John Connor");

Il est possible de remplacer le nom ci-dessus par l'un de votre choix, ceci n'a aucune incidence sur le bon fonctionnement du workflow. Lors de la définition de notre workflow, nous avons défini une propriété Nom dans la classe partielle du workflow. La clé de notre argument dans le dictionnaire a le même nom que cette propriété. En fait, lorsque l'on passe le dictionnaire au moteur pour instancier notre workflow, celui-ci va, après avoir instancié le workflow, chercher, via la réflexion, la propriété du workflow s'appelant exactement "Nom" et va lui donner la valeur, ici "John Connor" en la castant, vu que c'est un objet dans le dictionnaire.

Il ne reste plus qu'à instancier le workflow et le lancer avec les 2 instructions suivantes :



WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(HelloWorld.Workflow1),arguments);
instance.Start();

La méthode CreateWorkflow prend en argument le type du workflow et le dictionnaire définissant les arguments. Elle renvoie un objet de type WorkflowInstance qui contient la méthode Start() permettant d'exécuter le workflow.

Finalement, ajoutez les 2 lignes suivantes pour attendre la fin d'exécution du workflow et de laisser le choix à l'utilisateur de fermer le programme.



waitHandle.WaitOne();
Console.ReadKey();

Si tout s'est bien passé, voici l'affichage que vous devriez avoir après avoir compilé et lancé le projet :


Pour une meilleure vision du code nécessaire à l'instanciation et l'exécution d'un workflow, voici l'intégralité du code :



using System;
using System.Threading;
using System.Workflow.Runtime;
using System.Collections.Generic;

namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
using(WorkflowRuntime workflowRuntime = new WorkflowRuntime())
{
AutoResetEvent waitHandle = new AutoResetEvent(false);
workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e) {waitHandle.Set();};
workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
{
Console.WriteLine(e.Exception.Message);
waitHandle.Set();
};

Dictionary<string,object> arguments = new Dictionary<string,object>();
arguments.Add("Nom", "John Connor");

WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(HelloWorld.Workflow1),arguments);
instance.Start();

waitHandle.WaitOne();

Console.ReadKey();
}
}
}
}

16 octobre 2008

Indexation MOSS : Faites attention à la page par défaut d'une collection de site

Actuellement, je travaille sur un portail intranet basé sur Sharepoint MOSS. Etant en phase d'intégration, je corrige les quelques bugs restants. L'un d'entre eux m'a particulièrement surpris concernant l'indexation d'une collection de site. Celle-ci s'arrêtait au bout de 3 items "crawlés", alors que la collection contient plus de 800 éléments à indexer.

Après une petite recherche dans les logs applicatifs du serveur, je suis tombé sur une erreur de type Warning, ASP.NET 2.0.50727.0, WebEvent :

Je remarque que dans les Request Information la page d'accueil (page par défaut) de la collection de site renvoie une exception de type NullReferenceException. Pourtant, dans la définition des sources à indexer, seuls 2 sous sites ont été spécifiés. La page par défaut ne devrait pas être indexée. Pour vérifier que la page par défaut de la collection de site est en cause, j'ai donc spécifié une autre page par défaut grace à une page Layout uniquement accessible par URL : [url collection de site]/_Layouts/AreaWelcomePage.aspx.

Après avoir effectuer la manipulation et relancer le crawl, tout est rentré dans l'ordre. J'ai donc du modifier la page par défaut en éditant un script C# codé de manière inline dans la page par défaut de la collection de site.

Moralité, faites attention à la page par défaut de votre collection de sites si vous souhaitez indexer le contenu des sous sites.

8 octobre 2008

Qu'est ce qu'un workflow ?

Pour répondre à cette question, je vais faire l'analogie avec le code d'un développeur. Lorsque l'on développe, on définit le plus souvent une suite d'opérations ou de tâches. Cela peut être une insertion dans un journal d'événements ou dans une base de données. Ensuite, on ordonne ces opérations avec des instructions qui contrôlent le flux de notre programme. Par exemple, on utilise l'instruction :

If (condition)
{Liste d'opération 1}
else {Liste d'opération 2}


ou un bloc :

While (Condition)
{Liste d'opérations}


pour traiter des données en boucle.

Chaque décision dans le code, que ce soit en C#, Java, C/C++, Javascript etc ..., est matérialisée par des conditions. Tout code source possède 3 types d'instruction:
  • Les instructions d'exécution qui peuvent être une écriture dans une base de données ou encore dans un journal d'évènements, l'envoi d'un mail, ou plus généralement toute instruction qui exécute une action.
  • Les instructions définissant le flux d'exécution comme le bloc if (condition) {instructions} else {instructions}. Ce sont toutes les instructions qui ont une influence sur le flux d'exécution du programme.
  • Les règles qui définissent les conditions dans les flux d'exécution.

Voici un exemple montrant les 3 types les 3 types d'instruction:

Les concepteurs de Workflow Foundation ont identifié ces 3 concepts et les ont intégrés dans Workflow Foundation. Un workflow n'est rien d'autre qu'une suite d'instructions d'exécution représentée par des activités (Custom Activity, Code Activity, InvokeWebService, etc…). Ces instructions d'exécution ou activités sont elles-mêmes comprises dans des activités définissant le flux d'exécution du programme (IfElseActivity, WhileActivity, ParallelActivity, ReplicatorActivity, etc…). Pour définir les conditions dans les activités définissant le flux d'exécution, on met en place des règles (rules en anglais). Pour illustrer cela, voici un workflow reprenant la même logique que l'exemple précédent :
Premièrement, le workflow est représenté de manière graphique. Les deux exemples réalisent exactement la même chose. Cependant, dans le premier exemple, tout a été défini avec un langage textuel qui est le C# alors que dans le deuxième, tout a été quasiment définit de manière graphique en utilisant le drag'n drop et la fenêtre de propriété de Visual Studio 2008.
Aujourd'hui, c'est dans l'air du temps d'utiliser ce que l'on appelle des langages graphiques ou des DSL (Domain Specific Language). Le plus célèbre d'entre eux est le designer de classes intégré dans toutes les éditions de Visual Studio. Workflow Foundation propose ainsi de définir votre propre langage graphique pour définir les workflows. Pour cela, nous avons à notre disposition un ensemble d'activités que l'on peut glisser et déplacer dans la fenêtre définissant le workflow. Voici un aperçu de la Toolbox regroupant les différentes activités fournies par WF :

Il est aussi possible de définir un workflow par le code, en instanciant les bons objets, ou avec du XAML (rebaptisé XOML dans WF, pour le différencier du XAML de WPF). Le XAML est un langage basé sur XML permettant d'instancier et de paramétrer des objets.
Lorsqu'on crée un nouveau workflow, nous avons le choix avec 2 implémentations de workflow :
  • Workflow basé sur du code uniquement
  • Workflow basé sur du XAML avec un fichier de code associé

Théoriquement, il est aussi possible définir un workflow basé uniquement sur du XAML en supprimant le fichier de code associé, Visual Studio 2005 ou 2008 ne propose pas ce template. L'intérêt principal d'un workflow définit uniquement en XAML, est que l'on peut le modifier à la main ou via un éditeur spécialisé sans avoir besoin de recompiler l'application. Cela laisse penser qu'il est possible développer des applications avec des workflows, et que votre client pourra via un éditeur de workflow, modifier par la suite le comportement de son programme en éditant lui-même ses workflows. Bien sûr, il devra avoir une connaissance du vocabulaire métier défini dans le workflow, mais comme celui-ci est la matérialisation d'un processus de l'entreprise, cela devrait lui être familier. Dans tous les cas, c'est beaucoup plus simple et plus réaliste que de modifier du code et de le recompiler.
Une autre différence fondamentale lorsqu'on utilise WF est que la logique définissant ce que l'on fait est séparée de la logique qui définit quand on le fait. C'est-à-dire que l'on peut changer toute la logique de séquencement ou d'ordonnancement d'un workflow sans affecter les instructions d'exécution. Pour cela, on sélectionne la ou les activités voulues et on les déplace à l'endroit souhaité dans le workflow. Avec un langage textuel, cela se résumerai à un ensemble de couper/coller quelque peu hasardeux et pas toujours très simple à réaliser.
Comme la logique d'ordonnancement est définie de manière déclarative et qu'en fait chaque activité correspond à une classe, il est tout à fait possible de modifier à l'exécution, c'est-à-dire dynamiquement, un workflow. Par exemple, on peut rajouter de nouvelles instructions d'exécution ou des instructions définissant le flux d'exécution à un endroit donné. Il suffit simplement d'instancier les activités voulues, de les paramétrer et de les insérer là où on le souhaite. Comparé à un langage textuel, cela pourrait être synonyme d'injection de code (via de la POA par exemple), ce qui est quand même bien plus complexe à réaliser.

5 octobre 2008

Utiliser les Custom Tools dans Visual Studio

Alors déjà, qu'est ce qu'un Custom Tool ?
C'est un utilitaire sous forme de librairie (dll) qu'utilise Visual Studio afin de générer quelque chose, généralement des classes, à partir d'un fichier en entrée. Chaque fichier présent dans une solution Visual Studio (2003,2005 et 2008) propose dans sa fenêtre de propriété un champ Custom Tool.

Sur un fichier de ressources de type resx, le Custom Tool ResXFileCodeGenerator est défini d'office. Son rôle est de générer une classe contenant le code pour accéder aux différentes ressouces déclarées dans le fichier XML. Voici une liste regroupant les plus populaires :
  • MSDiscoCodeGenerator : Génère une classe proxy permettant d'accéder simplement à un web service à partir de son fichier wsdl
  • WCF Proxy Generator : A partir d'un fichier .svcmap, ce custom tool génère la classe d'accès au service
  • SettingsSingleFileGenerator et ResXFileCodeGenerator : Génère une classe proxy vers un fichier de configuration ou de ressources
  • EntityModelCodeGenerator : Génère l'ensemble des classes d'accès aux données (Entity Framework) à partir d'un fichier edmx
  • MSLinqToSQLGenerator : A l'instar de EntityModelCodeGenerator, ce custom tool s'occupe du fichier XML (.dbml) créé par le designer de LinQ to SQL.
  • MSDataSetGenerator : Créé un ensemble de fichier XML et de classes définissant un Dataset, à partir d'un fichier XSD
Les Custom Tools permettent de gagner un temps conséquent (plusieurs milliers de lignes de code peuvent être générées d'une passe) et garantissent une certaine qualité dans le code produit. Il est à noter que cela fonctionne que dans un seul sens (unidirectionnel). Modifiez un fichier généré par un Custom Tool et celui-ci sera écrasé à la prochaine génération.
Il est aussi intéressant de savoir que l'on peut redéfinir le Custom Tool attaché à n'importe quel fichier. Du coup, si le code créé par un Custom Tool ne vous plait pas, rien ne vous empêche de créer le votre et de l'attacher au fichier source. Sur le site suivant, vous trouverez une démonstration avec le WCF Proxy Generator : http://blogs.msdn.com/pedram/archive/2007/08/10/customising-wcf-proxy-generation-in-visual-studio-2008.aspx.
Alors comment savoir si vous avez besoin de créer votre propre Custom Tool ? Et bien, si vous avez un ou plusieurs fichiers XML dans vos projets C# ou VB .Net, et que vous devez créer du code pour accéder aux informations stockées dedans, un Custom Tool adapté vous permettra de vous passer de cette tâche un peu fastidieuse. Je les ai d'ailleurs utilisés pour un projet Sharepoint. Une solution typique WSS ou MOSS contient bien souvent un nombre impressionnant de fichiers XML (features, fields, ONET.XML, etc ...), et il est souvent utile de pouvoir accéder facilement à certaines de leurs informations.
Voici quelques liens si vous souhaitez créer votre propre Custom Tool :