Portage du BlogEngine.Net sur Azure réussi

by Aymeric Weinbach 29. janvier 2009 17:18

A tout seigneur, tout honneur, dans ce premier post, je vais vous parler du moteur de Blog que vous utilisez actuellement (et de sa mise en oeuvre sur "Azure")...

Petite présentation du Blogengine.net

Le BlogEngine.net est un projet open source assez populaire disponible sur Codeplex .
Comme base de départ pour un site le projet me paraissait intéressant parce qu’il apporte un tas de fonctionnalités essentielles, il est multi-contributeurs, permet de fédérer le contenu d’autres blogs, et a toutes les fonctionnalités qu’on attend d’un blog moderne : ajax, rss, microformat, antispam pour les commentaires, url rewriting etc...  Il contient par ailleurs des widgets sympas comme l’indispensable « TagCloud »  ainsi que la possibilité de rédiger des posts offline avec le support de Windows Live Writer.  Pour connaitre toutes les fonctionnalités la liste est ici.

J'ai donc utilisé VS 2008 pour déployer une instance de BlogEngine sur Azure. En quelques étapes, voici ce que j'ai du faire...

Architecture de BlogEngine.net

le projet est composé de :

C’est une solution vs 2005 composé d’un projet de  site web (BlogEngine.Web) et d’une bibliothèque de classes (BlogEngine.Core). 

BlogEngine alias BE.net a une architecture ou les couches d’accès aux données sont bien séparées du reste du projet en implémentant un système de provider similaire à ceux livrés dans le framework ASP.Net tel que le Membership Provider pour la gestion des utilisateurs. BlogEngine.Core propose notamment un XmlProvider qui permet de stocker les données dans des fichiers XML et un DbBlogProvider qui permet de stocker les données dans une base de données SQL. Enfin, une classe abstraite BlogProvider dont on héritera pour implémenter son propre provider, ainsi qu’une collection qui contient tous les provider disponibles pour un site.

Architecture d’une application Windows Azure

Après avoir  installé les Visual Studio Tools for Windows Azure on accède aux nouveaux templates de projets suivant:


Dans la mesure où BlogEngine.Net est un site Web, nous pourrons nous contenter d’utiliser le template de Web Cloud Service.
Il est composé de :

Le premier est un projet de déploiement et de configuration et à la simulation de l’environnement azure en local. Il démarre la development fabric et le development storage et permet de simuler le load balancer et de lancer plusieurs instance de son web role en local.
Le second, le «web role» est un projet d’application web modifié pour Azure.  
Pour plus d’informations sur le contenu du projet de configuration et du «web role», consultez cet article du blog de Redo qui fait partie d’un tutorial sur Windows Azure.

Voici l’architecture de notre application Web hébergée sur Azure, lui permettant de s’adapter aux pics de charge.

Mon premier réflexe, a été d’essayer d’insérer un projet « blank cloud service » dans ma solution BE .net. Mais impossible d’y associer le projet web avec le « blank cloud service » on ne peut l’associer qu’avec un «web role» ou un «worker role» apparemment. Avec un projet d’application web il est possible en éditant le fichier .csproj de le transformer en «web role» en suivant la manipulation décrite dans ce billet du blog de David Rousset. Dans notre cas de figure c’est impossible le projet BlogEngine.Web étant un projet de site web et ne contenant pas de fichier .csproj.

Donc pour porter BlogEngine.net sur Windows Azure 2 tâches s’imposent : 

  • Ecrire un nouveau provider pour stocker les données dans le « cloud »
  • Transformer le projet de site web du BE .net en «Web Role»

Création du provider

La voie qu’il m’a semblé la plus simple au début était de reprendre l’XmlProvider proposé par BlogEngine.Net qui propose de stocker les données du blog dans des fichiers Xml. La tâche se résumait donc à l’adapter pour stocker les fichiers Xml dans les blobs de l’Azure Storage.
Cela me permettait de découvrir ainsi le Blob Storage sur Azure. 

Dans le SDK de Windows Azure, on trouve parmi les Samples  le projet StorageClient qui encapsule l’api REST de l’Azure Storage (Table, Queues et Blob). J’ai donc commencé à écrire mon provider en utilisant le StorageClient.

Rapidement je me suis demandé si c’était vraiment la meilleure solution. J’avais peur de mauvaises performances puisque quand je voulais récupérer des données je devais d’abord récupérer via le réseau un fichier XML qui était potentiellement d’une taille importante, charger le fichier en mémoire et utiliser le parser XML. Comme le but d’une application Azure est d’être scalable, il me fallait aussi implémenter un mécanisme d’écriture concurrentielle puisqu’il ne fallait pas que si le site tourne sur plusieurs instance, qu’une instance écrase le travail réalisé sur le XML par une autre. En fait le blob storage permet de créer des blobs d’une taille allant jusqu'à 50 GB et fournit un mécanisme de mise à jour conditionnel qui permet de gérer les accès concurrents. Après quelques tests d’utilisation du XML stockés dans des blobs je n’étais pas entièrement satisfait du résultat. Ma conclusion est que le Blob Storage est comparable au systéme de fichiers, c’est possible de développer un provider XML stocké dans des blobs. Mais pour les données structurées rien ne vaut une base de données que ce soit dans le “cloud” ou ailleurs, pour des raisons de performance aussi bien que d’intégrité structurelle des données. J’ai donc décidé de changer d’approche.

Pour le stockage dans le “cloud”, aprés cette analyse il me restait 2 possibilités :

  • le Table Storage de Windows Azure 
  • Les Sql Data Services qui font aussi de la plateforme Azure comme on peut le voir ici

Après quelques recherches, je suis tombé sur quelqu'un qui avait commencé à écrire un provider pour BE.net mais qui lui utilisait les SQL Data Services. Son travail m’a beaucoup aidé et m’a servi de base pour mon provider. Et c’est cela qui m’a décidé à choisir le stockage dans les SQL Data Services. Je ferais un prochain billet plus axé sur les SQL Data Services ou je rentrerais dans le détail du provider.

Migration du projet Web

Maintenant passons à l’étape suivante transformer le projet de « site web »  de BE.net  en « web role ».Un web Role est basiquement un projet d’ « application web » modifié pour Azure. Il y a quelques différences notables entre un projet « site web » et un projet « application web » :

  • Un site web pour chaque fichier ASPX contient  2 fichiers : 1 fichier aspx et 1 fichier .apsx.cs contenant le code Behind les 2 fichiers pouvant être réunis en 1 seul dans ce type de projet. Une application web contient un fichier de plus un fichier aspx.designer.cs qui contient la définition de  chaque contrôle utilisé dans l’aspx
  • Dans un site web on ne peut mettre des fichiers de classe que dans le dossier App_Code. Dans une application web c’est comme dans n’importe quelle application .net  on peut mettre des classes dans n’importe quel dossier qui sera associé au namespace du même nom.
  • Une application web est plus stricte qu’un site web dans un même namespace on ne peut
    Avoir une page du même nom qu’une seule fois. Ce qui posera problème on le verra dans BE .net.

Vous pourrez voir la liste complète des différences dans cet article (en anglais) ou sur le site du msdn.

Commençons  les choses sérieuses, j’ai donc ajouté dans ma solution un « webcloudservice » avec un « webrole » et recopié le contenu de BlogEngine.web dans mon « webrole » .

Dans le web.config on peut y trouver cette ligne :
<pages  enableSessionState="false" enableViewStateMac="true" enableEventValidation="true">
   
<controls>                       
        <add namespace="Controls" tagPrefix="blog"/>
    </controls>
</
pages>


Ca ne marchera plus dans un webrole il faut y mettre le chemin complet du namespace . Pour plus de séparation du code. Je crée une nouvelle bibliothèque de classe que j’appelle AzureBlogUI dans lequel je vais y recopier le dossier se trouvant dans App_Code. Et remettre toutes les classes dans 1 nouveau namespace AzureBlogUI.UI.Controls pour les contrôles. Je mets une référence vers cette dll dans mon webrole et je modifie le web.config ainsi.
<pages enableSessionState="false" enableViewStateMac="true" enableEventValidation="true">
   
<
controls>                       
        <
add namespace="AzureBlogUI.UI.Controls" tagPrefix="blog" assembly="AzureBlogUI"/>
    </
controls>           
</
pages>

Maintenant je peux convertir les pages en application web pour cela j’utilise « «convert to web application » cet assistant va créer les fichiers designer.cs pour tout les fichiers du site.

Enfin le serveur web de  Windows Azure qui va héberger mon projet  étant un IIS7 je dois faire quelques petites adaptations à mon web.config  les modules et les handlers nécessitent une nouvelle configuration  spécifique. Il faut rajouter le tag <system.webServer> et la configuration suivante pour les handlers et les module de BE .net :

<system.webServer>
    <modules>
       
<
remove name="Profile"/>
       
<
remove name="AnonymousIdentification"/>                 
        <
add name="WwwSubDomainModule" type="BlogEngine.Core.Web.HttpModules.WwwSubDomainModule, BlogEngine.Core" preCondition="managedHandler"/>                 
        <
add name="UrlRewrite" type="BlogEngine.Core.Web.HttpModules.UrlRewrite, BlogEngine.Core" preCondition="managedHandler"/>                 
        <
add name="CompressionModule" type="BlogEngine.Core.Web.HttpModules.CompressionModule, BlogEngine.Core" preCondition="managedHandler"/>                 
        <
add name="ReferrerModule" type="BlogEngine.Core.Web.HttpModules.ReferrerModule, BlogEngine.Core" preCondition="managedHandler"/>           
    </
modules>           
    <
handlers>                 
        <
add name="blogml.axd_*" path="blogml.axd" verb="*" type="BlogEngine.Core.Web.HttpHandlers.BlogMLExportHandler, BlogEngine.Core" preCondition="integratedMode,runtimeVersionv2.0"/>                 
        <
add name="monster.axd_*" path="monster.axd" verb="*" type="BlogEngine.Core.Web.HttpHandlers.MonsterHandler, BlogEngine.Core" preCondition="integratedMode,runtimeVersionv2.0"/>                 
        <
add name="opml.axd_*" path="opml.axd" verb="*" type="BlogEngine.Core.Web.HttpHandlers.OpmlHandler, BlogEngine.Core" preCondition="integratedMode,runtimeVersionv2.0"/>                 
       
<
add name="rating.axd_*" path="rating.axd" verb="*" type="BlogEngine.Core.Web.HttpHandlers.RatingHandler, BlogEngine.Core" preCondition="integratedMode,runtimeVersionv2.0"/>                 
        <
add name="js.axd_*" path="js.axd" verb="*" type="BlogEngine.Core.Web.HttpHandlers.JavaScriptHandler, BlogEngine.Core" preCondition="integratedMode,runtimeVersionv2.0"/>                 
        <
add name="css.axd_*" path="css.axd" verb="*" type="BlogEngine.Core.Web.HttpHandlers.CssHandler, BlogEngine.Core" preCondition="integratedMode,runtimeVersionv2.0"/>                 
       
<
add name="rsd.axd_*" path="rsd.axd" verb="*" type="BlogEngine.Core.Web.HttpHandlers.RsdHandler, BlogEngine.Core" preCondition="integratedMode,runtimeVersionv2.0"/>                 
       
<
add name="metaweblog.axd_*" path="metaweblog.axd" verb="*" type="BlogEngine.Core.API.MetaWeblog.MetaWeblogHandler, BlogEngine.Core" preCondition="integratedMode,runtimeVersionv2.0"/>                 
       
<
add name="opensearch.axd_*" path="opensearch.axd" verb="*" type="BlogEngine.Core.Web.HttpHandlers.OpenSearchHandler, BlogEngine.Core" preCondition="integratedMode,runtimeVersionv2.0"/>                 
        <
add name="pingback.axd_*" path="pingback.axd" verb="*" type="BlogEngine.Core.Web.HttpHandlers.PingbackHandler, BlogEngine.Core" preCondition="integratedMode,runtimeVersionv2.0"/>                 
        <
add name="trackback.axd_*" path="trackback.axd" verb="*" type="BlogEngine.Core.Web.HttpHandlers.TrackbackHandler, BlogEngine.Core" preCondition="integratedMode,runtimeVersionv2.0"/>                 
        <
add name="sitemap.axd_*" path="sitemap.axd" verb="*" type="BlogEngine.Core.Web.HttpHandlers.SiteMap, BlogEngine.Core" preCondition="integratedMode,runtimeVersionv2.0"/>                 
       
<
add name="syndication.axd_*" path="syndication.axd" verb="*" type="BlogEngine.Core.Web.HttpHandlers.SyndicationHandler, BlogEngine.Core" preCondition="integratedMode,runtimeVersionv2.0"/>                 
       
<
add name="image.axd_*" path="image.axd" verb="*" type="vBlogEngine.Core.HttpHandlers.ImageHandler, AzureStorage" preCondition="integratedMode,runtimeVersionv2.0"/>                 
       
<
add name="file.axd_*" path="file.axd" verb="*" type="BlogEngine.Core.Web.HttpHandlers.FileHandler, BlogEngine.Core" preCondition="integratedMode,runtimeVersionv2.0"/>           
    </handlers>           
    <
validation validateIntegratedModeConfiguration="false"/>
</system.webServer>


Et je laisse un seul théme dans le dossier Themes en excluant les autres , le probléme étant que les pages site.master sont toutes dans le même namespace et ont le même nom de classe, sinon j’aurai un probléme de compilation.

Après je peux rajouter mon AzureBlogProvider dans le web .config et retirer les autres devenus inutiles.

<BlogEngine>
    <blogProvider defaultProvider="AzureProvider">
       
<
providers>                       
           
<
add name="AzureProvider" type="BlogEngine.Core.Providers.AzureBlogProvider, AzureProvider"/>
           
<!--
<add name="XmlBlogProvider" type="BlogEngine.Core.Providers.XmlBlogProvider, BlogEngine.Core"/>-->                              </providers>
   
</
blogProvider>
</
BlogEngine>


BE.Net à besoin aussi d’un MembershipProvider et d’un RoleProvider pour la gestion des utilisateurs. J’utilise un MembershipProvider et un RoleProvider dont les données sont aussi stockées dans SQL Data Services. Il a été fait avec le même code de base que l’AzureBlogProvider.

 <membership defaultProvider="AzureMembershipProvider">
   
<
providers>                       
       
<
clear/>                       
       
<
add name="AzureMembershipProvider" type="BlogEngine.Core.Providers.AzureMembershipProvider, AzureProvider" description="Azure membership provider" passwordFormat="Hashed"/>                       
       
<!--
<add name="XmlMembershipProvider" type="BlogEngine.Core.Providers.XmlMembershipProvider, BlogEngine.Core" description="XML membership provider" passwordFormat="Hashed"/>-->                 
   
</
providers>           
</
membership>           
<
roleManager defaultProvider="AzureRoleProvider" enabled="true" cacheRolesInCookie="true" cookieName=".BLOGENGINEROLES">                      <providers>                       
       
<
clear/>                       
       
<
add name="AzureRoleProvider" type="BlogEngine.Core.Providers.AzureRoleProvider, AzureProvider" description="Azure role provider"/>                       
        <!--
<add name="XmlRoleProvider" type="BlogEngine.Core.Providers.XmlRoleProvider, BlogEngine.Core" description="XML role provider"/>-->                 
    </
providers>
</
roleManager>


Restera quelques petits problèmes provoqués par l’hébergement Windows Azure :

Si on utilise les Sql Data Services, en rajoutant la “service reference” vers l’url des SQL Data Services https://database.windows.net/soap/v1/. Plusieurs “bindings” WCF seront rajoutés dans le web.config dans la section <system.serviceModel>. Les sites executés dans Windows Azure doivent être compatible avec le mode “Partial Trust”. Et la zone Bindings du web.config n’est pas supporté en “Partial Trust”. Il faut faire son binding “à la main” dans son code. Un code d’exemple est disponible ici.

<%@ Register Src="/User controls/PostList.ascx" TagName="PostList" TagPrefix="uc1" %>

L’espace dans le “Src” est mal interprété par Windows Azure ça fonctionne en local mais une fois déployée une exception se produit. Il faut soit remplacer l’espace par un %20 ou renommer le dossier sans espace et mettre à jour le “Src” comme ça :

<%@ Register Src="/User_controls/PostList.ascx" TagName="PostList" TagPrefix="uc1" %>

Enfin j’ai trouvé un bug sur Windows Azure la ligne de code suivante :

context.Request.Url.GetLeftPart(UriPartial.Authority)

normalement renvoie la partie gauche de l’url par exemple ici http://www.zecloud.fr

Mais sur Azure sans doute à cause du load balancer on récupère http://www.zecloud.fr:20000

Et l’adresse n’existe pas, c’est probablement une adresse virtuelle utilisé par Azure, le même problème se produit si on utilise un Response.Redirect() avec une url relative en paramétre.

Pour l’instant pas de solution miracle si on supprime le port qui est après l’url, cela ne fonctionne plus sur le poste de développement. La seule bonne solution pour l’instant que j’ai trouvée consiste à avoir une clé dans le web.config qui correspond à l’environnement d’execution et à executer un  code spécifique en fonction de la plateforme (production ou développement).

En conclusion

Lle portage d’une application web existante dépendra de l’architecture utilisé. Si c’était un projet “application web” en .net 2.0 utilisant des données externes fournies par un Web Service la migration sera extrêmement simple. Pour un projet “site web” dont les couches d’accés aux données seraient bien séparés du reste du code la principale difficulté consistera à refaire la couche “données” mais avec les outils fournis cela s’avére finalement assez facile. L’expérience permettra d’établir des “Best Practices” pour développer dans le “Cloud”, aussi bien au niveaux des données, que au niveau applicatif.

Actuellement noté 5.0 par 4 personne(s)

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

BlogEngine.Net | Asp.Net

Commentaires

Ajouter un commentaire


(Affichera votre icône Gravatar)  

  Country flag


  • Commentaire
  • Aperçu immédiat
Loading



Powered by BlogEngine.NET 1.4.5.0 Ported On Azure by Aymeric Weinbach
Theme by Mads Kristensen

Le canal du moment  

18:20 Ronny : Ce flux me fait penser à tweeter ;)
11:01 Steven : En effet, ce serait bien :) Cela apporterai plus de visibilité pour la communauté !
17:28 Aymeric : Quand on écrive sur un canal que ce soit automatiquement publié sur Twitter ou/et sur Facebook. Qu'est ce que vous en pensez?
01:21 Aymeric : Merci Stève, et j'en ai profité pour rajouter un lien direct sur le rss du "canal du moment"
16:09 Stève : J'allais proposer un flux RSS mais c'est déjà présent. Bravo Aymeric
18:48 Ronny : Diffuser un/des pptx associé(s) à un canal
00:56 Aymeric : Sur ce canal venez participer au brainstorming sur l'Azure Sensor.Et si vous vous demandez ce qu'est L'Azure Sensor. C'est à l'heure actuelle ces canaux de discussions. A vous d'y apporter vos idées, ou votre participation :)
16:26 Ronny : Azure Sensor c'est génial!!