Azure : Faire cohabiter du PHP et du .NET sur un même site, c'est possible !

azure_logo Dans Azure, le cloud de Microsoft, il est possible de faire héberger un site web qui contient des pages .NET et PHP.
Avec le recul, ça peut paraître évident. Mais j’ai été surpris au premier abord.
Dans l’onglet « Configuration » du site, il suffit de vérifier si les 2 frameworks sont activés :
azure_1
(Désolé, c’est une capture de l’ancien portail Azure, mais je ne peux pas encadrer le nouveau portail avec son UX horrible)
Du côté de Visual Studio, ça se présente ainsi, pas besoin d’installer une extension du genre PHP Tools, il suffit d’ajouter ses fichiers PHP à la solution :
azure_2
Je me suis aperçu de tout cela en implémentant un back-end web-services en C# sur ma vieille appli PHP des années 90. Et ça marche nickel !
NB : Je ne serai pas étonné qu’on puisse également ajouter du JAVA et du Python, afin d’obtenir une application web œcuménique où les langages cohabiteraient en paix.

Call a Powershell script from c# code

PowerShell logo This solution given here :
executing-powershell-scripts-from-c is fine, but only works with .NET 4.0 or above…
In my case, I needed to call a powershell script from a c# code source running under .NET 3.5.
So, this is a function that uses a Process objet to run Powershell :

public static int RunPowershellScript(string ps)
{
int errorLevel;
ProcessStartInfo processInfo;
Process process;
processInfo = new ProcessStartInfo("powershell.exe", "-File " + ps);
processInfo.CreateNoWindow = true;
processInfo.UseShellExecute = false;
process = Process.Start(processInfo);
process.WaitForExit();
errorLevel = process.ExitCode;
process.Close();
return errorLevel;
}

This code is synchronous, and has a lack of prerequisites checks, but it can be reusable in a more elegant code 😉

Combien ça rapporte de développer un jeu pour mobiles ?

Et bien pas grand chose !
Attention, on parle d’un jeu de base sans prétention développé par un clampin sur ses heures de sommeil.
Prenons Colorseeds pour Windows Phone : il est gratuit, et rapporte quelques millièmes de centime d’euro à chaque fois qu’une publicité s’affiche (bannière en haut de l’écran de jeu).
Prenons le mois d’avril :
– Colorseeds est présent dans plus de 180 pays, et il a été mis en avant (= plus de visibilité sur le Store) environ 60 fois (dont des pays à fort potentiel comme les Etats-Unis et le Canada),
– Il a été téléchargé 8290 fois, dont 1000 en France (c’est très peu !),
– Plus d’un million de publicités ont été affichées (c’est beaucoup ! Les gens qui ont téléchargé ont beaucoup joué),
– Le CPM (« coût par mille »), c’est à dire le nombre d’euros que me rapporte 1000 publicités affichées est de : 0.05€ (pas terrible),
– Ce qui donne : 1 543 138 pub * 0.05 CPM / 1000 = 51€ environ…
Bref, il me faudrait plus d’une vingtaine de jeux pour gagner un SMIC… Ou alors investir dans de la promo massive !
pubcenter_colorseeds_avril2014
 
Pour les heureux possesseurs d’un Windows Phone, Colorseeds se télécharge ici :
Download Colorseeds for Windows Phone !

Utilisation du type de variables "Decimal"

« Decimal » a été créé pour avoir une précision plus grande, mais dans une plage de valeurs plus restreinte.
Il est préconisé dans les applications monétaires, tandis que les autres types à virgules flottantes (double, single) sont destinés aux calcules scientifiques (et ils ont une vitesse de traitement plus rapide).
Fait amusant :

Decimal numerateur = 1m;
Decimal denominateur = 3m;
Console.WriteLine(numerateur / denominateur * denominateur);

Le résultat sera 0.999999… Ceci parce que Decimal est justement trop précis !
L’utilisation de Round permettra de profiter de la précision de Decimal, tout en faisant les arrondis nécessaires. Comme cité dans cette ressource : « Le type Decimal n’élimine pas la nécessité d’arrondir. Il réduit plutôt des erreurs en raison de l’arrondi ».

Console.WriteLine(Math.Round(numerateur / denominateur * denominateur, 2));

Ceci retournera 1.00.
L’utilisation des autres flottants n’est pas conseillé. Car les erreurs d’arrondis vont plus faciement s’accumuler à fur et à mesure des calculs.
Cet exemple ci-dessous ne doit pas fausser l’opinion des développeurs :

Double numerateur = 1f;
Double denominateur = 3f;
Console.WriteLine(numerateur / denominateur * denominateur);

Le résultat sera de 1 ! Mais cela reflète simplement un manque de précision de la division avec ce type de flottants : numerateur / denominateur = 0.3333343
 

SharePoint 2010 : Workflow d'envoi de mails déclenché par un utilisateur anonyme.

Les utilisateurs anonymes ne sont pas autorisés à déclencher certains flux de travail (workflow), notamment ceux qui envoient des mails.
Prenons l’exemple d’une liste qui déclenche un workflow d’envoi de mail sur l’ajout d’un élément :
Workflow_1
Ici l’envoi de mail sera un échec, car la tâche d’envoi de mail nécessite plus de droit que ne peut avoir un utilisateur anonyme.
Pour remédier à cela, une solution serait de faire déclencher le workflow par un événement tiers :
Workflow_2
Il suffit d’insérer un event-handler qui va prendre en charge l’événement « ajout d’un élément » à la place du workflow, et qui va forcer le démarrage du workflow « à la main ».
Le code de l’event-handler pourrait ressembler à cela :

public class List_EventReceiver : SPItemEventReceiver
{
 /// <summary>
 /// Un élément a été ajouté, on lance le workflow avec privilèges.
 /// </summary>
 public override void ItemAdded(SPItemEventProperties properties)
 {
    base.ItemAdded(properties);

        // Exécution en élévation de privilège
    SPSecurity.RunWithElevatedPrivileges(delegate()
   {
      using (SPSite site = new SPSite("http://mon_site"))
      {
        using (SPWeb siteWeb = site.OpenWeb())
        {
          SPList list = siteWeb.Lists["Ma_Liste"];
          SPWorkflowAssociation wf = list.WorkflowAssociations.GetAssociationByName("Nom_du_Workflow", System.Globalization.CultureInfo.CurrentCulture);
          // Lancement du workflow
          site.WorkflowManager.StartWorkflow(list.GetItemById(properties.ListItemId), wf, wf.AssociationData);
        }
      }
   });
 }
}

ASP.NET : L'impersonation

Mécanique de l’impersonation en ASP : pratique, mais pas simple…
L’impersonation signifie le fait de choisir sous quel compte seront exécutés les processus ASP.
Dans le web.config, la ligne suivante :
<identity impersonate="true" />
Signifie que le compte qui s’est connecté à IIS sera utilisé pour exécuter la logique côté serveur.
ATTENTION : Si le site est en accès anonyme, alors c’est le compte du pool d’application qui est utilisé (il en faut bien 1 !).
On peut forcer le compte utilisé pour l’impersonation :
<identity impersonate="true" userName="DOMAINusername" password="aaa"/>
Ceci n’empêche pas le compte Windows de se connecter au site, mais ce sera DOMAINusername qui exécutera la logique.
Du côté des sources :
HttpContext.Current.User : Renvoie le compte connecté à IIS (Windows intégré, anonyme, etc.) Quelque soit l’impersonation choisie.
WindowsIdentity.GetCurrent() : Renvoie le compte qui exécute la logique. Donc si l’impersonation est à « true » dans le web.config, on aura le même compte que Current.User…
Enfin, en cours d’excution, on peut décider que le compte connecté sera celui qui sert à l’impersonation :
WindowsIdentity ident = (WindowsIdentity)HttpContext.Current.User.Identity; // On caste avant...
ident.Impersonate();
Si jamais le Current.User était un accès anonyme, une exception surgit : « Une identité anonyme ne peut pas exécuter un emprunt d’identité. »
CQFD.

ASP.NET : Renvoyer un ContentType différent (excel, CSV, doc…)

ASP.NET : Renvoyer un ContentType différent (C#)
Quelques conseils quand une page renvoie un flux différent que du  "text/html" :

  • Préciser l’encoding : Response.ContentEncoding = System.Text.Encoding.Default;
  • Générer le contenu dans le Page_Load, et finir par : Response.End();
  • Exemple :

 protected void Page_Load(object sender, EventArgs e)
{

Response.Clear();
// Type de contenu de la réponse
Response.ContentEncoding = System.Text.Encoding.Default;
Response.Charset = "iso-8859-1";
Response.ContentType = "text/csv";

// Entête
Response.AddHeader("Pragma", "no-cache");
        // Force le navigateur à faire un "enregistrer sous..."
Response.AddHeader("Content-Disposition", "attachment; filename="fichier.csv"");
// Corp de la réponse
Response.Write("bla bla bla");
// Pas de cache
Response.CacheControl = "no-cache";
Response.Expires = -1;
Response.End();

}

NB : Dans le corps de la réponse, pour faire des retours à la ligne « propres », il faut utiliser : System.Environment.NewLine

SharePoint 2007 : Package d'une solution WSP

La solution est la solution.
Je recommande fortement de packager le moindre developpement SharePoint dans une solution WSP. C’est à mon avis la pratique la plus sûre pour ne pas avoir de mauvaise surprise lors de la remontée d’un serveur après un crash.
Les outils :
VSeWSS – Visual Studio Extensions pour WSS : Add On pour Visual studio qui fournit les templates de dev. pour les workflows, sites ou listes. La solution la plus puissante (« deploy » package et ajoute et déploie la solution sur le serveur !).
WSP Builder : Le meilleur moyen de packager une solution WSP. Disponible sous CodePlex. Il suffit d’organiser convenablement sa solution Visual Studio, WSP Builder fait le reste !
STSDEV : Même type que WSP Builder, mais apparement plus contrignant et obsolète.

.NET : Génération d'une image à la volée

I believe I can (make an image on the) flyyyyyyy !
Imaginons qu’on veuille afficher une image qui changerait d’aspect à chaque chargement de page (graphiques, clavier virtuel pour l’authentification…). Il ne serait pas pratique de stocker ces images temporaires côté serveur (il faudrait être sûr de donner un nom unique aux images pour éviter les conflits par exemple).
La solution qui suit permet d’éviter de stocker des images sur le serveur. On va créer une page ASPX qui retourne un flux de type MIME « image/*** ».
Dans la page qui affichera l’image générée, il faut une balise de la sorte :
<img src="GenerateurImage.aspx" alt="" />
La page GenerateurImage.aspx ne contient rien (ou le minimum).
Tout se passe dans la procédure page_load du code-behind (
GenerateurImage.aspx.cs) :
public void Page_Load(object sender, System.EventArgs e)
{
// On peut partir d'une bitmap existante, ou en générer une nouvelle :

           Bitmap oBit = new Bitmap(???);
Graphics g =
Graphics.FromImage(oBit)


// Ici on modifie l'image avec toutes les méthodes offertes par la classe Graphics.
// On pourrait éventuellement utiliser des paramètres de la requête (ID=...)
pour rendre la chose encore plus dynamique !
// On change le type de flux de sortie
Response.ContentType = « image/jpeg »;
// On encode (compression)
ImageCodecInfo myImageCodecInfo;
Encoder myEncoder;
EncoderParameter myEncoderParameter;
EncoderParameters myEncoderParameters;
myImageCodecInfo = GetEncoderInfo(« image/jpeg »);
myEncoder = Encoder.Quality;
myEncoderParameters = new EncoderParameters(1);
// Niveau d’encodage élévé.
myEncoderParameter = new EncoderParameter(myEncoder, 100L);
myEncoderParameters.Param[0] = myEncoderParameter;
oBit.Save(Response.OutputStream, myImageCodecInfo, myEncoderParameters);
oBit.Dispose();
// Pas de cache pour cette page.
Response.CacheControl = « no-cache »;
Response.AddHeader(« Pragma », « no-cache »);
Response.Expires = -1;
}
En bonus, la fonction qui ramène le codec :
        private static ImageCodecInfo GetEncoderInfo(String mimeType)
{
int j;
ImageCodecInfo[] encoders;
encoders = ImageCodecInfo.GetImageEncoders();
for (j = 0; j < encoders.Length; ++j)
{
if (encoders[j].MimeType == mimeType)
return encoders[j];
}
return ';
}